NiceGUI的中文入门教程

NiceGUI的中文入门教程0 前言1 环境准备1.1 运行环境1.1.1 基础环境初始化1.1.2 NiceGUI运行环境1.2 开发工具1.3 自托管文档【可选】2 入门基础2.1 认识NiceGUI2.1.1 NiceGUI的Hello World!2.2 NiceGUI的基本结构2.2.1 图形界面的基础概念2.2.1.1 控件2.2.1.2 布局2.2.1.3 交互2.2.2 NiceGUI与基础概念的对应2.3 NiceGUI中不得不学的功能2.3.1 文本控件2.3.1.1 ui.label2.3.1.2 ui.link2.3.1.3 ui.element2.3.1.4 ui.markdownui.html2.3.2 常用控件2.3.2.1 ui.button2.3.2.2 ui.input2.3.2.3 ui.sliderui.knob2.3.3 多媒体控件2.3.3.1 ui.imageui.interactive_image2.3.3.2 ui.iconui.avatar2.3.3.3 ui.audioui.video2.3.4 图形布局2.3.4.1 ui.rowui.columnui.grid2.3.4.2 ui.space2.3.4.3 ui.separator2.3.4.4 ui.menuui.context_menu2.3.4.5 ui.tooltip(2025.01.08更新)2.3.4.6 ui.notify2.3.5 属性绑定和数据控件2.3.5.1 binding2.3.5.2 ui.linear_progressui.circular_progress2.3.5.3 ui.spinner2.3.5.4 ui.code2.3.6 外观美化2.3.6.1 需要记住具体名字的propsclassesstyle方法2.3.6.2 好记一点的tailwindcss属性和Tailwind对象2.3.6.3 暗黑模式ui.dark_mode2.3.6.4 设置主题颜色ui.colors2.3.7 事件和执行2.3.7.1 通用事件2.3.7.2 常用事件app.on_*2.3.7.3 定时器ui.timerapp.timer2.3.7.4 UI更新和可刷新方法ui.refreshable(2025.01.08更新)2.3.7.5 运行JavaScript代码ui.run_javascript2.3.8 网站页面2.3.8.1 ui.page2.3.8.2 页面布局2.3.8.3 ui.navigate2.3.8.4 ui.download2.3.9 运行配置2.3.9.1 ui.run2.3.9.2 native mode3 高阶技巧3.1 with的技巧3.2 slot的技巧3.3 TailWindCSS的技巧3.4 自定义控件3.4.1 通过继承NiceGUI现有控件来创建新控件3.4.2 使用Quasar的标签定义新控件3.4.3 使用VUE自定义新控件(2025.01.08更新)3.5 for循环的技巧3.5.1 用for创建多个有规律的控件3.5.2 与lambda表达式组合使用时的问题3.5.3 更好的for循环3.6 绑定的技巧3.6.1 绑定到字典3.6.2 绑定到全局变量3.6.3 性能优化3.7 app.storage的技巧3.8 修改指定元素的技巧3.8.1 ui.query3.8.2 ui.teleport3.8.3 ElementFilter3.8.3.1 初始化方法3.8.3.2 within方法和not_within方法3.8.3.3 exclude方法3.8.3.4 传送控件到匹配结果3.8.3.5 总结3.9 其他布局3.9.1 ui.list3.9.2 ui.splitter3.9.3 ui.tabs3.9.4 ui.scroll_area3.9.5 ui.skeleton3.9.6 ui.carousel3.9.7 ui.expansion3.9.8 ui.pagination3.9.9 ui.stepper3.9.10 ui.timeline3.9.11 ui.notification3.9.12 ui.dialog3.9.13 ui.menu补充3.9.14 ui.tooltip补充3.10 其他常用控件3.10.1 ui.dropdown_button3.10.2 ui.button_group3.10.3 ui.badge3.10.4 ui.chip3.10.5 ui.radio3.10.6 ui.toggle3.10.7 ui.select3.10.8 ui.checkbox3.10.9 ui.switch3.10.10 ui.range3.10.11 ui.joystick3.10.12 ui.textarea3.10.13 ui.number3.10.14 ui.color_picker3.10.15 ui.color_input3.10.16 ui.date3.10.17 ui.time3.10.18 ui.upload3.11 多媒体控件的使用技巧3.11.1 ui.interactive_image的交互技巧3.12 ui.add_* 和app.add_*的技巧3.12.1 app.add_static_fileapp.add_static_files3.12.2 app.add_media_fileapp.add_media_files3.12.3 ui.add_head_htmlui.add_body_html3.12.4 ui.add_cssui.add_scssui.add_sass3.13 ui.keyboard的事件处理技巧3.14 其他数据展示控件3.14.1 ui.table3.14.2 ui.tree3.14.3 ui.log3.14.4 ui.editor3.14.5 ui.codemirror3.14.6 ui.json_editor3.14.7 ui.scene3.14.8 ui.aggrid3.14.9 ui.highchart3.14.10 ui.echart3.14.11 ui.pyplot3.14.12 ui.matplotlib3.14.13 ui.line_plot3.14.14 ui.plotly3.14.15 ui.leaflet3.15 多任务与异步的技巧(2025.01.08更新)3.15.1 异步(2025.01.08更新)3.15.1.1 异步支持3.15.1.2 ui.clipboard和剪贴板3.15.2 后台任务(2025.01.08更新)3.16 版本亮点3.16.1 app.timer——2.9.0版本新增4 具体示例【随时更新】4.1 app.*4.1.1 app.shutdown4.2 app.native4.2.1 app.native.settings4.3 ui.*4.3.1 ui.run4.3.2 ui.refreshable4.4 ui.button4.5 ui.page4.6 ui.stepper4.7 ui.icon4.8 ui.carousel4.9 ui.tree

0 前言

对于Python语言的使用者来说,NiceGUI是一款优秀的WebUI、GUI框架,只需学习一定量的前端知识,就能使用NiceGUI快速搭建出美观的UI界面。但是,由于官方作者不提供系统性的入门、中文教程,很多中文初学者望而却步。于是,本教程应运而生。

本教程旨在用中文提供官方文档没有的系统性入门教程,并将部分社区讨论问题汉化、简化,方便中文学习者尽快上手并解决常见难题。虽然教程的名字叫入门教程,但本教程并没有停留在翻译官网文档的阶段,能够解决常见问题的高阶技巧也有。对于官方提供的各个控件的详细API,本教程并不会照本宣科,而是在提供思路之后,由读者自行查阅。正所谓“授人以鱼不如授人以渔”,掌握方法比掌握结果更有效。

1 环境准备

本章主要介绍运行和开发NiceGUI程序的环境准备,包括虚拟环境的建立、开发工具的选择、如何自托管文档。

1.1 运行环境

为了保证最佳开发体验,所有的环境准备优先使用Windows系统,使用Linux、Mac的话,请自己根据系统变通。

首先,需要准备P解释器和pip。其中,Python解释器是运行后续代码、工具的基础,只需到官网安装最新稳定版(当前为3.12.*)即可;pip是Python官方的包管理工具,安装解释器时务必勾选此选项,如果没有勾选或者想要后续单独安装pip,可以到pypi官方查看安装方法,这里不在赘述。

环境管理工具有pdm和poetry,使用以下命令全部安装:

环境管理工具是快捷管理Python运行环境的工具,可以创建出独立的Python运行环境,各个运行环境内安装的软件包不会干扰其他环境,也不会影响到默认的Python环境。

pdm是一款国人创建的环境管理工具,语法简单,操作方便,因此教程采用此工具。

poetry也是一款环境管理工具,是NiceGUI官方仓库采用的,可以基于官方源代码自己编译安装包,也用于后续自托管文档,因此需要安装。

但是,poetry的检查依赖速度比pdm慢太多,故这里采用pdm作为学习工具,如果读者有能力,可以只使用poetry。

1.1.1 基础环境初始化

先在纯英文、无空格、无特殊字符的路径下创建纯英文、无空格、无特殊字符的空白文件夹,进入该文件夹后,右键,在此处打开终端或者命令行,运行以下命令:

会看到以下输出:

过程为交互式,需要自己输入之后回车才能继续,不输入直接回车则采用默认。

Please select (0):为选择Python版本,pdm会自动识别当前电脑安装的所有Python解释器,部分工具(如gcc)也会自带Python解释器,需要正确选择自己安装的、可以直接运行pip命令的Python解释器,这一步根据实际情况选择,一般默认第一个,可以直接回车。

Project name (chinese_guide_of_nicegui_for_beginner):为设置项目名称,通常为当前文件夹名字,可以自己输入来修改。这个不会修改当前文件夹名字,只会影响项目描述文件中的项目名称和后续创建的源代码文件夹名称。这里可以直接回车。

Project version (0.1.0):为设置当前项目的版本号,该版本号符合语义化规则,不懂或者不想了解的可以直接回车。

If yes, it will be installed by default when running pdm install. [y/n] (n):这一步是问你要不要将项目构建成分发包(可以用pip安装的),如果选择y,使用pdm install就会默认安装项目。这里选择不创建分发包,所以直接回车。

License(SPDX name) (MIT):Author name ():Author email ():分别是许可协议、作者名字、作者邮箱地址,了解、知道的可以修改,不清楚或者不想写的可以直接回车。

Python requires('*' to allow any) (==3.12.*):为Python的版本要求,如果后续要用低版本或者高版本Python运行,这里需要修改,否则此项目会限制Python的版本。这个后续可以自己了解,这里直接回车即可。

至此,基础环境已经准备完毕,可以得到以下目录结构:

./src/chinese_guide_of_nicegui_for_beginner/下存放项目的源代码,后续的代码操作(创建修改)均在此目录。此外,项目根目录下有pyproject.toml(项目描述文件)和README.md(自述文件),本教程不涉及手动修改。

1.1.2 NiceGUI运行环境

基础环境初始化完毕之后,项目还不能直接运行基于NiceGUI框架的代码,因为基础环境还没有安装NiceGUI。因此,需要在项目根目录下使用以下命令安装:

如果运行环境是全局环境或者想用pip安装,可以执行下面的命令安装:

安装过程取决于网速,耐心等待。

如果后续项目中需要使用其他库,可以使用pdm add 库对应的pip安装命令中的名字来添加到项目环境中。

对于调试使用NiceGUI的程序,通常在native mode下比较方便,因此,建议安装pywebview来增加native mode的支持,命令是:

如果运行环境是全局环境或者想用pip安装,可以执行下面的命令安装:

对于没有安装过Microsoft Edge WebView2或者版本较旧的Windows系统,建议访问 Microsoft Edge WebView2下载 安装最新版本。

1.2 开发工具

VSCode或者PyCharm,其中VSCode比PyCharm轻量,但需要手动安装Python插件,而PyCharm自带插件,操作简单。这里推荐使用VSCode,比较流畅,如果是使用PyCharm,后续操作根据VSCode对应即可。

对于VSCode,建议安装以下插件:

1.3 自托管文档【可选】

因为官网文档是可交互的,只有连接到官网才能操作。但是,部分地区的访问官网存在网络不佳的情况,如果需要自托管官网文档,可以遵循以下步骤。

首先要安装git,具体可以看Git - 安装 Git

安装完成后,额外找一个空白文件夹,打开终端,执行以下命令:

没有git,可以打开以下链接:

下载压缩包之后解压,结果一样。对于GItHub难以访问的问题,可以自行搜索GitHub加速的方法。

使用以下命令进入NiceGUI的源代码文件夹,并执行安装操作:

等安装完成之后(poetry的install时间会比较长),执行以下命令,会开启一个服务器托管官网文档,并自动调用浏览器打开自托管的官网:

注意:如果不使用poetry创建的虚拟环境运行,而是使用全局Python运行的话,首先要安装NiceGUI库,然后根据提示安装缺失的库(自托管文件需要的部分依赖不会随NiceGUI自动安装),并且在源码根目录执行python main.py

关掉终端就可以关闭服务器,下次运行这条命令就可以直接开启自托管的官网文档,无需再次安装。

如果官方有源代码更新,后续将源代码解压覆盖之后,执行一次安装操作即可更新。

2 入门基础

本章主要介绍NiceGUI的基础知识,系统性了解NiceGUI的基本结构,在自学NiceGUI、查阅官网文档时有方向。

2.1 认识NiceGUI

NiceGUI是一个开源的Python库,可以搭建运行在浏览器的图形界面,也就是WebUI,也可以理解为和网站一样。NiceGUI的学习过程并不难,但不意味着你可以零基础入门。对于开发NiceGUI的开发者而言,Python的基础是必须的;如果掌握Python不常用语法和用法更好,后续在使用NiceGUI的过程中,可以很方便理解一些为了达成效果而使用的骚操作。尽管大部分Python的学习者是零基础入门,除了转全栈、爬虫的开发者,后续学习过程中很少接触前端(HTML、CSS、JavaScript),但还是在使用NiceGUI之前,要有学习前端的心理准备。NiceGUI为了方便Python工程师快速搭建图形界面,专注于Python代码本身,做了不少前端细节的隐藏。不过,默认的样式不一定符合预期效果,为了达成效果,还是需要了解一些前端知识,才能让界面完全符合心意。

NiceGUI的底层使用了FastAPI作为运行服务器,Quasar作为前端框架,支持TailWindCSS的CSS语法,因为Quasar的内部使用VUE搭建,NiceGUI也不可避免地用到了VUE的语法。因此,对于想要用好NiceGUI的开发者,在后续使用NiceGUI的过程中,需要对上述提到的项目有一定了解。想要对后端部分定制、修改的,需要了解FastAPI以及其基于的其他组件;想要让界面美化、随心的,需要掌握Quasar、TailWindCSS基础;对于有能力和需求完全定制界面的,则需要掌握VUE基础。

相关链接:

NiceGUI官方文档:https://nicegui.io/documentation

FastAPI官方文档:https://fastapi.tiangolo.com/zh/

Quasar官方文档:https://quasar.dev/docs

TailWindCSS官方文档:https://tailwindcss.com/

VUE官方文档:https://cn.vuejs.org/guide/introduction

2.1.1 NiceGUI的Hello World!

如果你能看到这里,证明你有学好NiceGUI的基础和能力,并不畏惧上一节提到的那么多基础知识。那么,就用一段简单的Hello World代码开启NiceGUI的入门之旅。

使用VSCode在项目文件夹下的src\chinese_guide_of_nicegui_for_beginner内,新建以.py为后缀的Python代码文件,复制以下代码到代码文件中并保存:

注意vscode的右下角,务必确保使用的是venv下的Python解释器,如果不是,点击右下角3.12.4{'.venv'}对应位置,选择输入解释器路径-查找,选择.venv\Scripts\下的python.exe

hello_world_vscode

点击VSCode右上角的运行按钮(三角形),就可以看到一个窗口弹出,点击SAY HI按钮,就能看到窗口底部弹出的Hello World!

hello_world

从示例可以看到,基于NiceGUI的Python程序寥寥三行代码,除去导入语句和必不可少的ui.run,主体只有一行代码,就能实现一个完整的交互过程,足见NiceGUI的强大、简洁。后续教程中很多例子都可以做到几行代码实现不错的效果,这也是NiceGUI大受欢迎的原因。

2.2 NiceGUI的基本结构

2.2.1 图形界面的基础概念

在正式学习NiceGUI之前,需要先对图形界面有个基础的理解。

一般来说,搭建图形界面需要理解三个概念:控件、布局、交互。

2.2.1.1 控件

控件是搭建图形界面的基本元素,就像是盖房子用的砖、门、窗等最小搭建单位。控件通常是图形界面框架提供、直接可用的。如果使用过程中发现基本元素不够,可以结合布局功能,用基本元素组合出新的控件。

2.2.1.2 布局

布局是排布控件的方式,就像是房屋的基本框架。用砖可以铺地,也可以垒墙,对于砖而言,墙或是地,就是布局。控件是横向排列还是竖向排列,是像网格一样一一对应,还是大控件套着小控件,都是由布局控制。大部分图形程序框架提供的布局类似,除了基本的几种布局之外,部分图形程序框架还提供额外的组合布局。

2.2.1.3 交互

交互是图形界面的重中之重,也是一个程序最难的部分。论难度的话,前面的控件和布局的学习只是对照文档,按图索骥,交互则需要身经百战,不断积累经验。

事件机制是目前大部分图形界面采用的交互反馈机制,也就是基于特定的事件触发,执行对应的函数。微软的winform中采用的消息机制,Qt的信号与槽,现代网页开发中的event事件监听,都可以理解为事件机制,只是对于winform和Qt而言,他们框架内的事件分别叫做消息和信号而已。

除了事件机制,美化也是交互的一部分。大部分现代图形界面框架。如Qt、WPF以及一系列基于网页开发的图形界面框架,支持CSS或者类似语法的美化功能,让图形界面变得更加美观,也让控件的动画效果更加丰富,这个极大提升了用户的使用体验。

此外,基于图形界面框架的特性,后端的处理逻辑以及数据的传递也是交互的一部分。在函数内,对于控件的控制,如何做到符合要求,毕竟有的框架、编程语言不支持没有定义或者声明函数就调用,而有的语言不支持声明函数。如果需要让控件显示的文本与另一个控件的文本一致,如何处理数据同步过程也需要技巧。

2.2.2 NiceGUI与基础概念的对应

对图形界面有基础的理解之后,下面就可以根据NiceGUI与基础概念的对应,进一步理解NiceGUI的设计理念。

Hello World!示例中,使用了导入语句from nicegui import ui导入了ui,顾名思义,ui就是用户界面,这也是NiceGUI调用控件的模块,也可以调用布局。具体的控件和布局用法可以参考下一节NiceGUI中不得不学的功能,下一节将选取一些NiceGUI中常用、不好理解的功能重点讲解。

因为NiceGUI是基于Quasar这个Web框架做的Python调用绑定,因此,NiceGUI的交互部分,很大程度与Web结合。在Web设计中,基于CSS实现的美化效果,基于JavaScript的event做的事件响应,都能在NiceGUI中看到。所以,如果用好NiceGUI,对Web三件套HTML、CSS、JavaScript的学习不可避免。此外,因为NiceGUI与Quasar这个Web框架的深度集成的关系,Quasar中的属性、slot、事件也需要掌握,才能让交互设计更加得心应手。

关于美化,下一节中的外观美化将会详细介绍,也可以查阅对应的官方文档。事件的学习,可以参阅下一节的事件和执行,也可以查阅对应的官方文档。

2.3 NiceGUI中不得不学的功能

以下是官网文档对于NiceGUI提供的功能做了大致的划分,本教程将会对每个部分中不好掌握、需要重点学习的控件、功能进行剖析:

  1. 文本控件:https://nicegui.io/documentation/section_text_elements

  2. 常用控件:https://nicegui.io/documentation/section_controls

  3. 多媒体控件:https://nicegui.io/documentation/section_audiovisual_elements

  4. 数据控件:https://nicegui.io/documentation/section_data_elements

  5. 属性绑定:https://nicegui.io/documentation/section_binding_properties

  6. 图形布局:https://nicegui.io/documentation/section_page_layout

  7. 外观美化:https://nicegui.io/documentation/section_styling_appearance

  8. 事件和执行:https://nicegui.io/documentation/section_action_events

  9. 网站页面:https://nicegui.io/documentation/section_pages_routing

  10. 部署与配置:https://nicegui.io/documentation/section_configuration_deployment

这部分对于官方内容的解析并不会完全覆盖,主要讲经常用到的参数、属性、方法,对于某些隐藏参数和不常用的属性方法,会在后面用到的时候详细介绍,这里最多提一嘴。

2.3.1 文本控件

文本类控件主要是一些静态展示可复制文本的控件,是构成网页显示效果的主要控件。

2.3.1.1 ui.label

文本标签,用法很简单,通过传入一个字符串类型的参数text,让网页显示字符串内的文字。注意,虽然参数支持多行文字,但是输出只能一行,需要原样输出多行文字的话,可以使用下面介绍的ui.html,将tag参数设置为'pre'

超链接,一种点击之后跳转到指定地址的文本元素。可以传入texttargetnew_tab三个参数。代码如下:

text参数,字符串类型,表示超链接显示什么文字。

target参数,字符串类型、page function类型、ui.element类型,表示超链接跳转到什么位置,这里只介绍字符串类型用法,其他类型可以自行探索或者后续需要用到的时候补充。字符串类型参数表示超链接跳转的url地址,可以使用协议开头的完整地址,也可以使用省略主机的绝对路径、相对路径。

new_tab参数,布尔类型,默认为False,表示要不要在新建标签页中打开超链接。

2.3.1.3 ui.element

通用元素,也是NiceGUI大部分界面控件的基类。很多控件都是通过继承这个类来调用自定义标签、JavaScript代码实现。通过继承实现自定义控件、修改默认风格属于高级用法,这里只说基本用法。

tag参数,字符串类型,默认为'div',表示生成的元素用什么HTML标签,实际使用时可以根据需要修改为其他HTML标签或者Quasar标签。代码如下:

move方法,将控件移动到指定控件之内,默认为'default'slot,也可以给target_slot参数传入slot name来指定slot。代码如下:

ui_element_move

2.3.1.4 ui.markdownui.html

ui.label类似,ui.markdownui.html,都可以用来展示文本,只是后两者支持markdown语法和HTML语法,因为markdown语法支持一部分HTML的标签,可以看到放在ui.markdown里的HTML标签也能被解析。以下是三种控件解析同一内容的代码:

此外,ui.html还支持传入字符串类型参数tag给基类ui.element,用于修改生成ui.html用的标签,比如:

2.3.2 常用控件

常用控件主要是一些支持点击、输入、拖动等交互功能的控件。

2.3.2.1 ui.button

按钮作是网页交互设计中最常见的基本元素,在移动互联网没有普及之前,使用鼠标点击为主要交互方式的时代,除了用于跳转网页的超链接,按钮就是网页中用的最多的可交互元素。在NiceGUI中,按钮控件可以传入位置参数text,关键字参数on_clickcoloricon

以下代码就是一个定义了基本交互的按钮,点击会弹出一个通知提示:

text参数,字符串类型,表示显示在按钮上的文字,如果是英文的话,默认全部大写。该参数默认只支持字符串类型,但是整数和小数可以直接使用,其他类型需要先转换为字符串类型才能传入。

color参数,字符串类型或者None,表示按钮的颜色,支持传入字符串类型的颜色类(Quasar、 Tailwind、CSS的颜色名)或者None(即让按钮变成默认颜色),默认为'primary',即和主题颜色一致。

icon参数,字符串类型,表示按钮额外显示的图标,支持传入字符串类型的图标名,具体名字会在ui.icon一节中介绍,这里不做详细介绍。

on_click参数,可调用类型,表示点击按钮调用的函数,可以使用lambda表达式,也可以使用函数名。

如果觉得对按钮传入参数来自定义按钮内容的方法太死板,也可以使用以下语法,使用上下文管理器关键字with来进入按钮的default slot(默认插槽),随意组合按钮内的内容:

button

对于on_click参数、后续会涉及到的'on'开头的参数、'on'开头的方法和on方法(函数名就是on)里的callback参数或者handler参数,均为可调用类型参数,既可以在创建控件时定义lambda表达式,也可以提前定义。对于复杂一点逻辑操作,应该定义函数而不是lambda表达式,比如:

对于ui.button等控件而言,除了支持通过传参创建响应动作,还支持调用对应的'on'开头方法(比如on_click)创建,这个方法极大提高了响应动作的灵活性,上面的例子就可以借助这个方法调整函数定义与按钮创建的先后顺序,代码如下:

2.3.2.2 ui.input

输入框,大概是网页中仅次于按钮和超链接,用得最多的控件。HTML中输入框的变体很多,在NiceGUI中,输入框的参数也很多,基于输入框扩展的控件也多,使用输入框的逻辑设计、注意事项一样很多。不过,在这一节,针对输入框的学习并不会那么深入,只要能掌握常用的参数和基础的方法,那些疑难点会放到进阶和需要的时候细讲,以免一时不好理解而记混。

以下代码定义了一个名字输入框和密码输入框,并在输入名字的时候,自动弹出通知显示名字和密码:

input

label参数,字符串类型,直译的话是标签,表示显示在输入框上方的文本,但不是输入的文本,如果输入的内容是空的,点击输入的之前会显示在输入框内,点击之后会移动到输入框上方。

value参数,字符串类型,表示输入框内的内容,也就是输入框的值。对于后续介绍的以及其他支持交互输入的控件,都会有这么一个参数、属性、相关方法,来设置控件的值,这一点上,NiceGUI的设计倒是很统一。

on_change参数,可调用类型,表示输入框的值变化时执行的函数。

password参数,布尔类型,表示输入框是否设置为密码输入框,如果设置为True,输入的内容将不显示明文,转而显示统一的密码符号。

password_toggle_button参数,布尔类型,表示输入框内是否显示密码按钮,密码按钮可以切换输入框内的密码、明文状态。

以下参数用得比较少或者有一定使用难度,学有余力的读者可以继续学习:

placeholder参数,字符串类型,表示输入框的占位符,即输入框没有输入之前,显示什么内容。与标签不太一样的是,占位符是输入框获得焦点时候显示的,标签是输入框没有获得焦点时候显示的。

autocomplete参数,字符串列表类型,表示在输入框输入内容时候,搜索这个字符串列表,来自动提示、补全要输入的内容,按Tab键可以补全内容。

validation参数,可调用类型、字典类型或者None,表示验证输入的内容是否有效。如果传入可调用类型参数,该参数返回错误信息表示内容无效,返回None表示内容有效。如果传入字典类型参数,则字典的键(key)表示错误信息,字典的值(value)返回True表示内容有效,返回False则表示内容有效并输出错误信息。默认值为None,表示不验证输入的内容。

2.3.2.3 ui.sliderui.knob

ui.slider是滑动条,这个比较简单,参数也不多,按理说不用专门讲一下,自学即可。至于ui.knob——旋钮,这个的样式参数比较多,也有很大的自定义自由度,值得一讲。不过,这些都不是这里要专门放在一起介绍的原因,具体原因是什么,这里先卖个关子,后面再做解释。

先看一段代码:

ui_knob

可以看到,ui.knob的前四个参数和ui.slider的一样,都是浮点类型,分别代表最小值、最大值、每次调整的最小步长、当前值。

ui.knob中,有colorcenter_colortrack_color三种参数可以修改颜色,分别代表旋钮边缘的颜色、旋钮中间的颜色、旋钮边缘没有覆盖之前的颜色,支持传入字符串类型的颜色类(Quasar、 Tailwind、CSS的颜色名)、None(即让按钮变成默认颜色)或者'primary'(跟随主题颜色)。

size参数是旋钮的整体大小,字符串类型,采用CSS语法的大小表示方式。

show_value参数,布尔类型,是否在旋钮中间显示当前值。

2.3.3 多媒体控件

2.3.3.1 ui.imageui.interactive_image

点开一个网页,最抓人眼球的是什么内容?没错,是图片。既然用NiceGUI设计网页,没有图片元素怎么行?在NiceGUI中,有两种显示图片的控件:ui.imageui.interactive_image。前者可以简单理解为显示图片的简单标签,后者是基于前者扩展了很多交互功能的plus版本。两者的第一个参数都是source,支持字符串类型的本地图像路径、网络图像路径,或者base64编码的图像本身,这个没什么难点,这里不做细讲,接下来要重点讲的是ui.interactive_image的其他参数的用法,因为这个控件有时候比看似简单的ui.image更加好用。

先看一段代码:

ui_img

可以看到,同样的图片地址,都是不传入其他参数的情况下,即使可用空间大于图片大小,ui.interactive_image也不会随着页面大小而缩放图片,始终保持图片的原始大小,这个有别于ui.image的特性,可以在日后想要保持图片真实大小时使用。

除了source参数外,ui.interactive_image还有以下参数:

content参数,字符串类型,表示覆盖在图片之上的SVG内容,SVG的画布大小就是图片的大小。当然,不太理解SVG的话也没关系,后面用到会详细介绍,也可以专门找一下资料。这里可以简单理解SVG为一种用定义描述的几何图形,这种图形不会因为缩放变成马赛克,因为它是基于定义绘制的。

size参数,元组类型(宽度,高度),表示如果source没有设置的话,这就是默认图形的尺寸。这个对于绘制上面的SVG内容来说比较重要,因为这个尺寸就是画布的大小。对于想要交互创建SVG内容的操作,指定画布大小很重要。

on_mouse参数,可调用类型,表示触发鼠标事件之后要执行的操作,默认包含一个鼠标事件的参数,参数字典内的image_ximage_y的值是以像素为单位表示的鼠标交互位置。

events参数,字符串列表,表示JavaScript订阅的事件,默认订阅点击事件,即['click'],也可以自己指定要订阅的事件内容。

cross参数,字符串类型或者布尔类型,表示要不要显示十字线来指示鼠标位置,默认为False。如果为True或者表示颜色的字符串,就会显示指定颜色(即字符串表示的颜色)的十字线。

除了上面的参数,还有一个force_reload方法,可以强制重新载入图片。这个方法两者图片控件都有,对于某些时候网络不好造成图片加载失败、支持刷新随机图片的接口,这个方法还是很实用的。

对于图片控件,一样可以使用with嵌入其他内容。比如,下面的代码就嵌入了一个按钮,实现了点击图片和按钮有不同的通知内容:

ui_interactive_image

2.3.3.2 ui.iconui.avatar

ui.button一节中挖了一个有关ui.icon的坑,现在,终于到了填坑的时候。先看一个简单的示例,了解一下ui.icon控件的用法:

ui_icon

ui.icon的参数不多,就三个:

name参数,字符串类型,表示图标字体中的图标名,ui.icon通过给定的图标名,从字体中加载图标,默认支持Material Icons图标字体。也可以自己添加其他图标字体,并结合对应图标字体的用法加载。这一部分需要根据具体情况灵活变通,这里不做详解。

color参数,字符串类型,表示图标的颜色。

size参数,字符串类型,表示图标的大小。

理解参数不难,但是,给出的示例代码并没有看上去那么简单。这个时候,聪明的读者已经发现了猫腻:几个图标看上去很像,但不完全一样。再仔细看的话就会发现,它们的后缀都是一样的,只有前缀不同。没错,这组图标本质上都是一个,只是名字的前缀有特定的风格含义,需要特别注意一下。

无前缀代表实心填充风格,'o_'前缀表示轮廓线风格,'r_'前缀表示圆角风格,'s_'前缀表示锐化风格,'sym_o_'前缀表示轮廓线符号,'sym_r_'前缀表示圆角符号,'sym_s_'前缀表示锐化符号。

这个时候,有的读者就要头疼了,前缀就这么几个,还好记,可图标那么多,每个图标名字怎么记得住,有没有方便查询的网站?有,不过国内没法直接访问,那就是官网:

Material Icons查询网站(非中国大陆地区):https://fonts.google.com/icons?icon.set=Material+Icons

当然,国内也有很多渠道查询,为了避免广告嫌疑,这里就不具体指明了。

不过,只是提供一个404网站,教程就失去了意义,这里还有一个备用方法,那就是小工具:

Material Icons查询器:

ui_icon_search

ui_icon_search2

可以在选择框里选择或者输入名字,得到该名字家族的全部图标,如果有的名字不支持全部风格的图标,会有英文提示。也可以点第二、第三个链接,查看对应图标的放大细节。点击图标,会自动复制全名。

也可以使用下面的icons模块,将代码存为icons.py,放到同目录下,使用from icons import Filled,filled,Outline,outline,Round,round,Sharp,sharp,Outline_symbol,outline_symbol,Round_symbol,round_symbol,Sharp_symbol,sharp_symbol来导入使用。首字母大写的是字典,语法为Filled['10k'],首字母小写的是函数,语法为filled('10k')。这两种使用方法都会返回准确的图标名字,在VSCode或者其他IDE中会弹出提示。

这一节到这里还没结束,还有一个控件要一并讲解。之所以放在一起讲,是因为这个控件主要参数和ui.icon一样,实际应用也不少,那就是头像控件——ui.avatar

按照惯例先看代码:

ui_avatar

从参数上看,icon就是ui.icon的name;颜色这里不太一样,colortext_color分别代表头像的背景颜色、图标颜色;size是头像大小,font_size是图标大小。此外,还有两个布尔类型的参数决定头像框的形状:square是方形,rounded是圆角方形,如果两个都是False,头像框就是圆形。

除了使用图标字体外,头像当然可以使用图片。一种是用以img:开头的图片地址,另一种是在default slot中加入图片控件。要注意的是,两种方法呈现的图片不一样,后者使用的是ui.image的样式,实际图片显示会有差别。

2.3.3.3 ui.audioui.video

ui.audioui.video从本质上讲是一样的,参数、方法基本一致,所以,只需学会其中一个的用法,另一个就是改个名字的事儿。

顾名思义,ui.audioui.video,分别代表着音频、视频控件,对于需要在网页播放音频、视频的情况,这两个控件再合适不过。

先看示例代码:

ui_video

控件通过字符串类型参数src传入视频地址,四个布尔类型参数controlsautoplaymutedloop分别代表着是否显示内置工具栏、是否自动开始播放、是否静音、是否循环播放。

当然,除了上面代码里用按钮在外面控制播放暂停,还可以监听播放、暂停、播放结束的事件,执行特定的操作。

2.3.4 图形布局

盖房子不是把砖简单地垒砌,做图形界面一样不是简单地添加控件,美观、简洁的布局除了让图形界面整体风格更加整齐,也让用户使用时更加方便、快捷,这才是图形界面的意义。在掌握了一定数量的常用控件之后,在继续深入学习其他控件之前,是时候了解一下NiceGUI支持的布局控件了。

在NiceGUI中,有一个概念非常重要,那就是上下文。前面代码中使用的with——Python的上下文管理器关键字,在NiceGUI中,就是进入某个控件的上下文,也就意味着后续添加控件的操作都是在这个控件的上下文内。直观感受的话,就是后续添加的控件都在这个控件内部。

以代码为例:

'Add label1'按钮在ui.card内部,它添加的ui.label就在ui.card内。'Add label2'按钮在ui.card外,它添加的ui.label就在ui.card外。所以点击两个按钮的效果是这样的:

auto_context

2.3.4.1 ui.rowui.columnui.grid

在图形界面的布局设计中,最常用也是最基本的布局,就是行布局、列布局和网格布局,几乎所有的现代GUI框架都提供了这三种布局。说来神奇,哪怕不用框架提供的其他布局,只是这三种布局相互组合,也能构成了用户在程序里看到的各种控件排布,真可谓“三生万物”。

ui.row(行)布局,即所有控件排成一行。

ui.column(列)布局,即所有控件排成一列。

ui.grid(网格)布局,即所有控件按照给定的网格排布(几行几列),依次占据每个单元格,就和表格一样。

layout_sketch

在NiceGUI中,想要让指定的控件按照给定的布局排布,就要在布局的上下文内创建控件,因此,这三个常用布局的代码就要这样写:

ui_layout

2.3.4.2 ui.space

在布局控件的时候,如果想要一些控件放在末尾,可按照布局控件的规则,内部的控件总是像排队一样一个挨着一个,似乎需要调整CSS才行。其实,有一种不用CSS的方法,那就是使用ui.space

ui.space可以默认填充浮动布局的可用空间,让布局不会因为默认的排列规则而显得末尾空空如也。

ui_space

2.3.4.3 ui.separator

ui.space可以填充空白,但是有读者担心,目的是达到了,能不能画条线在末尾的控件前,用来区分一下这个位置的控件与其他位置控件的区别?没问题,ui.separator就是用来干这个的,不管是水平还是垂直,都可以。代码如下:

ui_separator

2.3.4.4 ui.menuui.context_menu

有用过图形程序的读者看到前面的内容,肯定联想到,ui.separator好像菜单里面用来分割退出功能与其他功能的分割符,NiceGUI有没有菜单?当然也有,普通的点击菜单ui.menu和右键菜单ui.context_menu。两个菜单的语法一致,只是触发方式不同。菜单的内部一般使用ui.menu_item当菜单项,一般可以当做按钮处理,因为它们的触发操作都是传给点击事件。

下面的代码给'menu'按钮创建点击菜单和右键菜单,可以分别用鼠标左击和右击查看弹出的菜单的异同,除了菜单内文字为了作出区分专门修改了之外,菜单的布局和样式一模一样,因此两种菜单可以很方便复用相同的内容:

ui_menu

2.3.4.5 ui.tooltip(2025.01.08更新)

ui.tooltip可以给控件添加一种光标悬停之后弹出的提示。一般是在给定控件的上下文添加,也可以用给定控件的tooltip方法来添加,代码如下:

但是要注意,如果想要美化ui.tooltip,只能给上下文添加的ui.tooltip美化,如果是用tooltip方法添加的,则不能美化。因为tooltip方法返回的是控件本身,而不是tooltip。可以看到,下面代码的tooltip方法后添加的美化,在按钮本身生效了,实际上按钮的tooltip并没有改变颜色:

ui_tooltip

如果想要获取到控件tooltip方法设置的tooltip,可以遍历控件来获取控件内部的其他控件,再判断控件是不是需要的类型:

如果使用后续会学到的ElementFilter方法,也可以简单快捷地设置控件内部的tooltip

2.3.4.6 ui.notify

通知提示控件,在前面ui.button中已经用过,虽然简单传递一个字符串参数的用法很好用,但是到这一节结束都不说一下的话,实在是对不起读者。毕竟,这个控件的参数可不止看上去那么少,隐藏在其中的参数也很有用。

代码如下:

ui_notify

参数有点多,但不必害怕,习惯用法只需记住第一个参数,用起来也简单,其他参数仅作了解。

message参数,字符串类型,信息文本,显示在通知中的主要内容。

position参数,字符串类型,通知出现的位置,有"top-left""top-right""bottom-left""bottom-right""top""bottom""left""right""center"可选,默认为"bottom"

close_button参数,字符串类型或者布尔型,是否显示关闭按钮,如果是字符串类型,关闭按钮的文字就是给定的文字,默认为False

type参数,字符串类型,通知的类型,有"positive""negative""warning""info""ongoing",默认为None,不是其中的任何一种。

color参数,字符串类型,通知的背景颜色。

multi_line参数,布尔类型,是否让通知内容以多行格式显示。

progress参数,布尔类型,是否显示通知消失的进度条。

caption参数,字符串类型,显示在信息文本下的说明文字。

timeout参数,整数型,通知自动消失的时间,单位毫秒,为0就是不消失,但是要确保close_button不是False,否则通知没法正常消除,影响用户体验。

spinner参数,布尔类型,是否显示转盘动画,默认为False

2.3.5 属性绑定和数据控件

2.3.5.1 binding

ui.sliderui.knob一节中,为了让ui.slider和ui.knob联动,使用了bind_value方法来实现,现在是时候详细了解了。

对于大部分控件来说,都有几个可以bind(绑定)的属性,以ui.button为例,有icontextvisibilityenabled几个属性可以绑定。在下面的代码中,就将ui.button这四个属性绑定到ui.select上,可以手动选择每个ui.select的值,让按钮实时发生变化:

ui_binding

bind_{属性名}方法有四个参数:

target_object参数,任意对象,是被绑定的对象。

target_name参数,字符串类型,是被绑定对象的属性名,以被绑定的ui.select为例,它的值就对应value属性。

forward参数和backward参数,可调用类型,是正向绑定和反向绑定的转换函数。因为绑定是双向的,除了在目标值改变的时候同步过来,本身的值改变还要同步过去,是两个方向的操作。两个参数的默认值都是lambda x:x,意思是双向传递过程不改变值,如果有需要,可以修改这两个参数,在传递的时候实现数据的转换和映射的改变。

要注意的是,bind_{属性名}方法返回的是控件本身,因此可以实现连续bind的操作,不需要每次bind都要写一次控件对象,NiceGUI的这个特性在实际开发中非常方便。

除了bind_{属性名}方法,对于同一属性,还有bind_{属性名}_to方法和bind_{属性名}_from方法。相比之下,后两者方法就像是把前者的两个方向拆分,分别对应着正向和反向,参数自然分别去掉了不需要的反向绑定backward参数和正向绑定forward参数。而NiceGUI实际上内部的实现,是bind_{属性名}方法执行了bind_{属性名}_to方法和bind_{属性名}_from方法,因此,三者的关系更像下面这张图:

binding_sketch

bind_{属性名}_to方法和bind_{属性名}_from方法的特性,可以参照下面代码的演示:

ui_binding2

代码中做了两个反向绑定和一个正向绑定到ui.button。实际操作的话就会发现,因为两个反向绑定的控件都是单向绑定,任意操作其中一个反向绑定的控件,都只会影响到ui.button和正向绑定的控件,不会影响到另一个反向绑定的控件。对于正向绑定的控件,只有ui.buttontext发生变化之后,正向绑定的控件才会发生变化,只是操作正向绑定的控件,ui.button和两个反向绑定的控件不会受影响。

2.3.5.2 ui.linear_progressui.circular_progress

前面ui.sliderui.knob介绍过滑动条和旋钮,肯定有聪明的读者想用这两个控件做条形进度条和环形进度条,但是,这一节的标题已经表明,有专门的进度条控件,使用滑动条和旋钮充当进度条就未免有些画蛇添足。更关键的是,滑动条和旋钮接受用户输入,如果想避免用户输入影响数据展示,还要将控件禁用,具体样式又要做诸多调整,很不方便。然而,进度条控件没有这些弊端,使用起来更加简单。显然,这一节要介绍的进度条控件更适合展示进度。

ui.linear_progressui.circular_progress分别是线性进度条和环形进度条,样子看上去就像滑动条和旋钮,但与滑动条和旋钮不同,这两个控件不支持交互输入,只接受数据输入,所以,要想实现简单的交互演示,需要用到上一节的bind方法,这里用的是bind_value_from。当然,如果有其他控件作为该控件的下游,还可以使用bind_value_to串联下去。

ui_progress

2.3.5.3 ui.spinner

spinner直译的话是陀螺,在实际使用中,其实是加载中常见的动画图标,这里就称之为加载动画控件。

先看代码:

ui_spinner

ui.spinnertypesizecolorthickness四个参数,分别是字符串类型的动画类型、字符串类型的整体尺寸大小、字符串类型的颜色和浮点类型的厚度(这个只有typedefault时生效)。

下面的代码展示了所有动画类型的ui.spinner,读者可以根据需要选择合适的ui.spinner

ui_spinner

2.3.5.4 ui.code

如果想要在网页上显示有语法高亮的代码块,除了截图之外,惯用的做法是将代码放到代码块里。在NiceGUI中,ui.code就是用来展示代码的。ui.code有两个字符串参数,content参数是代码主体,language参数是代码的语言类型。

ui_code

用于数据展示的控件还有很多,比如:表格ui.table,树形图ui.tree,基于AG Grid的ui.aggrid,用于绘制图表ui.echartui.highchartui.pyplotui.line_plotui.plotly,展示3D模型的ui.scene等。因为用法稍微有点复杂,有能力的读者可以自己了解或者等基础学完之后学习后续的进阶教程,这里就不做讲解了。

2.3.6 外观美化

哪怕控件的默认样式好看,总有人觉得厌烦,调整颜色、大小、排列方式,或者一点小小的位移,都能让人眼前一亮。可是,对于前端来说的改变很小,需要涉及到的经验、代码很多,不去深入学习的话似乎很难达到效果。好在有NiceGUI,它的前端样式可以使用TailWindCSS,提前定义的一系列样式省去不少麻烦。当然,它也提供了多种自定义的接口,也没有因为降低难度而限制了前端的自由。

2.3.6.1 需要记住具体名字的propsclassesstyle方法

在掌握便利的工具之前,先辛苦一下,学点需要基础知识、但以后会很有用的方法。

先看代码:

styling

代码中,使用了三种方法将按钮颜色设置为红色。下面将分别说明三种方法的用法:

props方法,设置的是HTML标签的属性,对于NiceGUI基于的Quasar来说,标签属性有不少是决定显示效果的,因此,在NiceGUI中,可以用这个方法设置对应底层组件的属性。就代码案例而言,具体属性名可以参考https://quasar.dev/vue-components/button ,属性值参考 https://quasar.dev/style/sass-scss-variables

classes方法,设置的是HTML标签的类(class)名,NiceGUI支持TailWindCSS,因此,classes方法可以使用TailWindCSS预定义的类,也可以使用自定义的CSS类。就代码案例而言,具体类名可以参考https://tailwindcss.com/docs/background-color

style方法,设置的是HTML标签的CSS属性,可以直接给组件设置CSS属性,如果有一定前端基础的话,可以使用该方法充分自定义,没有相关基础也没关系,上面的两个方法提供了丰富的预定义功能,学习难度也不高。就代码案例而言,具体语法可以参考 https://developer.mozilla.org/en-US/docs/Web/CSS/background-color

2.3.6.2 好记一点的tailwindcss属性和Tailwind对象

TailWindCSS官网提供了搜索功能,但总归需要一定的时间学习,更别说网络不好或者没网的时候,没法打开官网搜索。当然,也有方法自托管TailWindCSS的文档,但有一定难度,也不符合NiceGUI的宗旨。

因此,NiceGUI后续推出了tailwindcss属性和Tailwind对象。无需死记硬背TailWindCSS,也不需要反复查询官网,直接使用tailwindcss属性或者使用Tailwind对象,会有自动提示,只要有一点英语基础,很容易找到对应的TailWindCSS类名。

比如:

tailwindcss

在开发工具中调用控件的tailwindcss属性或者创建Tailwind()对象之后,再输入.,就会弹出子方法提示,选择对应的子方法之后,再输入引号,会进一步弹出参数的提示。

2.3.6.3 暗黑模式ui.dark_mode

现在很多网站支持暗黑模式,方便访问者光线不佳时浏览网页,让网页没那么刺眼。NiceGUI中,也有一个控件可以快速切换暗黑模式,实现同样的效果,那就是ui.dark_mode

以下代码用ui.dark_mode创建了一个暗黑模式的控件,默认值为False,并且创建了一个模式切换的响应函数,会在模式切换时弹出通知,通过点击下方的两个按钮,可以直接启用、禁用暗黑模式:

2.3.6.4 设置主题颜色ui.colors

前面讲了如何给单个控件设置颜色的方法,肯定有读者担心了,如果想要修改主题颜色,还需要每个控件修改一次或者添加一下主题类,会不会很麻烦?其实,这个操作也有简单的方法,ui.colors可以直接设置整个程序的主题颜色,直接影响所有控件。因为Quasar提供了一系列预先定义好的类名,这些名字用在NiceGUI的控件颜色中,通过ui.colors可以修改这些类名的具体值。类名可以参考https://quasar.dev/style/theme-builder

以下面的代码为例,primary就是各个控件主要的颜色,通过ui.colorsprimary修改为#555,上一节中的按钮默认颜色就变成灰色了:

ui_colors

 

2.3.7 事件和执行

2.3.7.1 通用事件

大部分控件都有预定义事件监听,比如,ui.buttonon_click点击事件监听,在传参或者调用方法时定义。除了这种已经定义的事件监听,每个控件还支持通过on方法创建任意事件监听,比如使用on方法创建点击事件监听,也可以创建鼠标进入、离开的事件监听。正如下面的代码所示:

2.3.7.2 常用事件app.on_*

如果想要针对浏览器端的连接、关闭,服务器端的启动、关闭,添加事件监听,就要导入app模块。app.on_connectapp.on_disconnect可以监听浏览器端的连接、关闭;app.on_startupapp.on_shutdown可以监听服务器端的启动、关闭。代码如下:

2.3.7.3 定时器ui.timerapp.timer

定时器可以根据给定的时间间隔,周期性执行指定函数。ui.timer有四个参数:浮点类型的时间间隔interval、可调用类型的执行操作callback、布尔类型的是否激活active、布尔类型的是否运行一次once

代码如下:

以下的内容按理来说属于进阶部分,就算不学习,也能满足基础开发需要。但是官方将此部分内容与基础部分放在一起解释,为了方便有相关需求的读者学习,特地将该部分内容与基础合并,并且进阶部分也会同步增加。不理解、不需要此功能的读者可以暂时忽略,等学进阶部分时再学也可以。

NiceGUI官方在2.9.0版本新增了app.timer定时器,虽然用法上和ui.timer一样,但其归属于app而不是ui,还是有所区别的。

为了理解区别,需要先运行以下示例代码:

app_timer_1

示例代码源于NiceGUI官方仓库的一个问题,这里稍微简化了一下。问题作者想要让按钮创建一个定时更新显示内容的定时器,然后用另一个按钮删掉创建定时器的按钮。就是这样听起来很简单的操作,结果在删掉按钮时,工作定时器好像被一并“删掉”了。导致删掉按钮之后,原本应该继续执行的显示更新操作随之停止了。

听起来很奇怪,像是一个问题,其实不是,一开始就没有必要让按钮创建定时器。定时器可以在按钮的响应函数之外创建,按钮只需启动(activate)、停止(deactivate)定时器即可。因为定时器(ui.timer)会自动关联创建定时器的UI组件,一般做法是在auto-index页创建定时器,定时器关联了auto-index页,而auto-index页一般不会被删掉(也不能删掉,会出问题),所以使用定时器不会出问题。如果是其他UI组件创建了定时器,删掉创建定时器的UI组件,同时会一并删掉定时器,这也就是问题的原因。

上面的示例代码更换成常规用法也可以,解决方法也不难,不过,NiceGUI官方还是为此增加了一个独立于UI组件的定时器——app.timer,既是对此问题的解决方案,也是对后续有类似需求的功能实现。

那么,上面的代码在基本不动的前提下,只需将ui.timer换成app.timer即可:

app_timer_2

2.3.7.4 UI更新和可刷新方法ui.refreshable(2025.01.08更新)

一般来说,当修改控件的属性时,NiceGUI会尝试更新控件的显示状态,比如标签的文本、输入框的内容、控件的style/classes/props属性等。当然,如果某些修改不会触发UI刷新,那就要调用控件的update方法来更新控件显示。如果需要更新的控件比较多,也可以使用uiupdate方法,该方法可以传入多个控件,一次性完成每个控件的更新,不用重复调用每个控件update方法的代码。

以下面的代码为例,如果不调用update方法,加载动画控件不会因为显示为新的大小:

但是,update方法有一定局限性,只能刷新控件自身,如果控件下嵌套其他控件,该方法不能确保子控件一并刷新。因此,可刷新方法ui.refreshable就派上用场了。

以下面代码为例:

定义了一个字符串列表a,用于存储字符串。函数row_ui会根据a的内容创建相同数目的ui.label。然而,按钮'A+1'要执行的函数里,会执行一次给a追加一个'A',也就是说,每点一次按钮,a里的'A'就会多一个。可是,即便函数clicked里已经添加了row.update()来刷新ui.row,每次点击按钮,屏幕上的ui.label并不会增加。

问题出在哪里?稍安勿躁,先修改一下代码,用@ui.refreshable装饰一下函数row_ui,然后将函数clicked里的row.update()换成row_ui.refresh(),再看看按钮点击的效果:

这一次,结果总算正确了,点一次按钮,屏幕上的ui.label增加一个。

为什么?

可刷新方法ui.refreshable装饰的函数,会多一个refresh方法,调用这个方法会触发被装饰函数的重新执行,相当于这一部分的UI全部重新绘制,并不会像update方法一样只是触发刷新。触发刷新只能检测到与控件相关联的事件,示例代码里定义的列表a,没有使用绑定方法与控件关联,只有执行迭代的时候才有不同,因此ui.row的刷新并不能实现预期效果。

一般来说,为了快捷创建有规律的控件而使用迭代方法,需要根据数据变化刷新显示,都要用ui.refreshable来重新绘制。当然,没有控件数量变化的时候也有需要使用ui.refreshable来重新绘制的情况,读者在遇到时见机行事。毕竟重新绘制比触发刷新的性能开销大,非必要的情况,还是不要制造需要重新绘制的情况。

2.3.7.5 运行JavaScript代码ui.run_javascript

NiceGUI既然是基于Web的UI,免不了与网页三剑客HTML、CSS、JavaScript打交道。前面提到过HTML的标签,也讲过了CSS的美化语法,JavaScript如何执行却没说过。现在,终于到了与JavaScript交互的时候。

ui.run_javascript可以运行任意JavaScript代码,方便在Python无法处理的时候,补充使用JavaScript的方法。当然,JavaScript毕竟是一门设计时就很仓促的语言,时至今日,哪怕ES标准正在解决JavaScript留下的烂摊子,也避免不了JavaScript糟糕的历史包袱。对于各位读者来说,非必要还是别执行JavaScript代码,但是这个执行的方法,不能不会。

字符串类型参数code就是要执行的JavaScript代码,浮点类型参数timeout是超时,有些JavaScript方法的执行时间比较长,可以修改这个参数来避免JavaScript执行触发超时而导致结果返回失败。

2.3.8 网站页面

2.3.8.1 ui.page

在继续下面的内容前,各位需要了解一个NiceGUI的概念——page页面。前面讲了不少控件,想要访问这些控件组成的页面,通常直接访问弹出的native mode窗口,当然,也可以直接在浏览器里输入地址。

但是,细心的读者就会发现,当用户打开多个浏览器页面访问这个地址或者多个用户打开同一地址的时候,任意一个页面进行交互,其他页面都会有反应,和印象中的网站页面不太一样,正常的网站页面不应该这样。

没错,前面的控件其实都布局在auto-index的页面下,也就是网站的根目录,即网址后面只有一个斜杠或者没有斜杠的情况(http://127.0.0.1:8000/ 或者http://127.0.0.1:8000)。前面提到过一个概念叫上下文,这里想要快速理解页面的概念的话,还是要正确划分上下文关系,后面才能更好应用页面设计。

默认情况下,没有添加ui.page装饰器的话,整个程序内的控件都在auto-index的页面下;即使添加了ui.page装饰器,没有在装饰器修饰的函数的上下文内,也会自动划分到auto-index的页面。

理解auto-index之后,现在,可以正式介绍ui.page了。

不同于auto-index的页面是多人共享,ui.page产生的页面是私有的,即每个人在打开的页面交互,不会影响其他人打开的页面。

以下面的代码为例:

访问http://127.0.0.1:8000/时看到的uuid64和访问http://127.0.0.1:8000/private_page时看到的uuid是不一样的,http://127.0.0.1:8000//private_pageui.page产生的,属于私有页面,每次打开(可以尝试刷新)之后,uuid4()都会运行一次,因此uuid会重新生成。而http://127.0.0.1:8000/下的uuid是程序启动之后就生成了,uuid4()只在程序启动时运行一次,因此每次打开http://127.0.0.1:8000/显示的uuid是一样的。如果想要http://127.0.0.1:8000/下的uuid和http://127.0.0.1:8000/private_page一样,就要创建一个path='/'ui.page

创建ui.page的方法很简单,使用Python中的装饰器,在@ui.page(path='/private_page',title='private_page')的下一行定义一个函数,函数的内部就是ui.page的上下文。

ui.page默认有很多参数,这里只讲常用的pathtitle

path参数,字符串类型,表示这个ui.page对应的服务器地址,即http://127.0.0.1:8000后面的部分。

title参数,字符串类型,表示这个ui.page的浏览器窗口标题。除了在定义ui.page时候设置标题外,还可以调用ui.page_title(title='title')来设置当前页的浏览器窗口标题。

2.3.8.2 页面布局

尽管前面已经说过图形布局,这里还是有必要介绍一下页面布局。不同于图形布局能被嵌套在其他布局、控件内,页面布局的上一层只能是ui.page,或者说页面布局不受上层布局影响(专指ui.page_sticky,虽然放在图形布局下不报错,但图形布局并不会生效)。

ui.headerui.footer

标题和页脚,就是浮动在页面最上面和最下面的区域,就像报纸的页眉和页脚一样,不过这里使用的单词是header,所以称之为标题。这两个控件的代码参数只有一处差异,不过一般不怎么用。平时用的最多的还是相同的几个参数,这里只说布尔类型参数value,这个参数决定了这两个布局的显示状态,可以通过初始化传参来决定默认状态,也可以通过后续使用set_value修改这个值来显示、隐藏,使用set_visibility也能达到同样的效果,toggle可以自动切换显示、隐藏。

ui_header_footer

ui.left_drawerui.right_drawer

左右抽屉,其实这两个布局是基于ui.drawer的包装,呈现出来就是左右边栏。既然是基于同一控件的包装,参数自然是一样的。与标题和页脚一样,主要关注的还是参数value,含义一样是表示控件的显示、隐藏。

ui_drawer

ui.page_sticky

便签,这是从字面意义上看,实际作用也和便签类似,放在这个布局内的内容,会出现在侧边或者角落,就像贴在显示器的便签一样。ui.page_sticky的参数里,用得最多的是:字符串参数position表示位置,浮点类型参数x_offsety_offset表示XY方向的偏移。

ui_page_sticky

2.3.8.3 ui.navigate

有了ui.page之后,访问不同的地址代表不同的页面,可以使用之前的ui.link来创建指向不同页面的超链接。那么,有没有方法来跳转不同页面而不用明显地摆出超链接?

也有,那就是这一节要说的ui.navigate

ui.navigate提供了四个方法,分别是back后退、forward前进、reload重新载入、to跳转到。除了跳转到之外,其他方法都没有参数,跳转到的参数和ui.link一样,具体可以参考前面的介绍。

2.3.8.4 ui.download

除了用ui.link提供链接让用户点击下载文件之外,还可以调用ui.download来触发下载。

ui.download有三个参数,常用的是前两个参数:字符串类型或者字节类型参数src来源,字符串类型参数filename文件名。

因为pywebview默认禁止下载,在测试上面的代码时,需要设置app.native.settings['ALLOW_DOWNLOADS']True来允许下载。

2.3.9 运行配置

2.3.9.1 ui.run

几乎每个例子都要用的ui.run有非常多的参数,可基础内容都快讲完也不说说参数的话,多少有点说不过去。接下来,针对ui.run里常用的几个参数说一下。

host参数,字符串类型,表示整个程序监听的ip,默认情况下是'0.0.0.0',即监听全部可用的本机ip,如果是native mode下,这个值默认为'127.0.0.1',即只监听本地ip。

port参数,整数类型,表示程序监听的端口。

title参数,字符串类型,表示程序启动后的默认窗口标题。

show参数,布尔类型,表示程序启动后是否自动唤起默认浏览器打开程序的地址,如果同时开启了native mode的话,这个参数会被强制禁用。

native参数,布尔类型,表示是否启用本地窗口模式,即调用pywebview来显示NiceGUI程序,看上去就和桌面程序一样。

reload参数,布尔类型,表示是否启用热重载。不知道各位读者有没有注意到,之前都没有设置这个参数,但是修改了程序并保存的话,保存都是很快就生效,不需要手动重启程序。没错,这个参数默认为True,即程序修改会触发程序自动重启,让界面修改即使体现,这一点倒是方便开发的时候调试界面。不过,这个参数在非开发过程中最好设置为False,以免频繁触发重启。

2.3.9.2 native mode

前面很多例子都是在native mode本地模式下调试,其实,native mode相关的设置也有很多需要注意的地方,下面就说一说常用的几个。

以代码为例:

默认本地模式窗口可以自由调整窗口大小,但是有些时候界面没做大窗口的布局,不喜欢用户调整窗口大小或者最大化,可以设置app.native.window_args['resizable']False,禁用本地窗口的大小调整功能。

有能力的读者肯定试过在浏览器窗口中启用开发者调试工具来调整界面效果。但是,在本地窗口中,虽然本质上也是浏览器,但想要调试界面效果的话,还要额外开个浏览器窗口,总觉得有些麻烦,没关系,本地窗口也是可以启用开发者工具的。将app.native.start_args['debug']设置为True,在启动本地窗口的同时,还会弹出一个开发者工具,和在普通浏览器里一样,方便同步调试界面。

在前面介绍过ui.download,这个功能是程序化控制下载文件的。但是,有的读者可能遇到过在本地窗口下,这个功能好像失效了,以为是NiceGUI的bug。其实不然,这个是因为pywebview默认禁止下载,可以设置app.native.settings['ALLOW_DOWNLOADS']True来允许下载。

前面讲了禁用窗口大小调整的方法,但是,如果在这种情况,还想控制窗口大小的变化,或者在没有禁用的情况下自由控制窗口大小,有没有办法?有,使用app.native.main_window.resize方法即可,参数就是窗口的宽度和高度。

ui.page一节介绍过ui.page_title方法来设置窗口标题,有没有一种方法设置窗口标题但是只针对本地窗口有效?有,app.native.main_window.set_title方法只设置本地窗口标题,不会影响浏览器的窗口标题。其实,ui.page_title方法里默认调用了一次app.native.main_window.set_title方法,如果没有特殊需求,只用ui.page_title方法更简单。

3 高阶技巧

NiceGUI的控件有很多,日常开发中,除了了解常用控件之外,不常用的控件也可以在学有余力的时候看看。当然,图形界面的开发不止对控件的了解,一些逻辑上的处理技巧,Python语言的特性与框架的结合,也是难免会遇到的难题。不过,不用怕,授人以鱼不如授人以渔,日常能遇到、能解决的难题,这里都有。

3.1 with的技巧

with可以嵌套使用,来实现类似HTML中div嵌套的效果,比如:

也可以缩减一行,让代码更加紧凑:

3.2 slot的技巧

其实,所有的with element都是修改了element中名为'default'的slot。基于这个操作原理,可以借用add_slot的方法,结合with的用法,优雅、快捷地美化元素,实现复杂的布局。

比如,ui.dropdown_button有两个slot,defaultlabel;其中,default就是默认的slot,常规方法就可以嵌入元素到弹出的下拉列表里,如果想要像修改ui.button一样修改ui.dropdown_button本身,则要修改ui.dropdown_button'label'这个slot,代码如下:

slot

3.3 TailWindCSS的技巧

不同于CSS定义中伪类在冒号之后来定义效果,在TailWindCSS中,美化悬停(hover)和激活(active),需要放在冒号之前,冒号后紧随着要对状态应用的效果。比如,要实现标签背景颜色的悬停为红色、点击为黄色,代码如下:

tailwindcss_1

类似的,还可以实现暗黑模式(dark)下的颜色定义,点击switch来切换暗黑模式的开关,可以看到标签在暗黑模式下的背景颜色为红色,非暗黑模式下的背景颜色为绿色,代码如下:

tailwindcss_2

在此基础上,还有一种根据屏幕宽度调整显示的技巧,就是将冒号前的单词换成代表屏幕宽度的断点smmdlgxl2xl。如果要让标签的宽度随窗口大小变化自适应,也就是小窗口宽度小一些,窗口越大,宽度越大,那么,代码可以这样写:

tailwindcss_3

然而,运行之后,可以看到上面的代码其实有问题,按照理解这样写是没错,但断点代表的含义是,大于这个屏幕宽度值才会应用这个样式,而且一次写这么多条,等屏幕宽度同时符合两条以上条件的时候,CSS就会处于竞争选择的状态,虽然样式上表现可能没问题,但规范要求应该明确断点范围,就好像写分段函数一样,必须明确区间。

所以,正确的根据屏幕宽度使用不同的样式应该这样写。使用max-*来表示最大到什么大小使用什么样式,使用冒号表示区间范围。于是,可以用sm:max-md:w-16来表示smmd的范围内使用w-16的宽度样式,具体代码如下:

3.4 自定义控件

3.4.1 通过继承NiceGUI现有控件来创建新控件

在Python中,可以通过继承来扩展现有类的功能,这个操作对于NiceGUI同样适用。

如果想要基于ui.button实现一个可以通过点击切换颜色的按钮,可以这样做:

继承现有的控件类ui.button,先在__init__内调用父类的初始化方法;然后增加_state属性,默认为False,用于保存状态;最后定义点击事件的响应调用自身的toggle方法。

增加toggle方法,在方法内实现每次调用就翻转_state属性,并调用自身的update方法来更新显示。

重写update方法,先要根据_state属性设定button的显示颜色(动态更新color属性,详见Quasar提供的API),调用父类的update方法更新显示。

代码如下:

toggle_button

3.4.2 使用Quasar的标签定义新控件

如果想要实现的功能比较复杂,但是Quasar提供了NiceGUI没有实现的组件,还有一种简单的方法创建新控件。

Quasar有一个浮动功能按钮Floating Action Button,但NiceGUI没有实现。浮动功能按钮在Quasar的使用代码是:

对应地,将HTML标签嵌套关系转换为Python代码,q-fab标签就变成了ui.element('q-fab'),代码如下:

ui_element_q_fab

3.4.3 使用VUE自定义新控件(2025.01.08更新)

如果有VUE基础,可以结合VUE创建新的控件,在VUE中定义界面和部分交互,比在Python中更自由。由于笔者不擅长VUE,以下来自官方示例的代码就不做完全详细的解释,只简单说一下思路。

counter.js内容为:

counter.py内容为:

测试代码为:

自定义控件的核心在counter.js文件中,由VUE暴露需要用到的属性和JavaScript方法,在counter.py文件中通过props属性接收和设置暴露的属性,使用run_method方法执行暴露出的JavaScript方法。如果在counter.js文件中发射($emit)了事件,还可以在counter.py文件中使用on方法响应对应的事件。

vue_1

3.5 for循环的技巧

3.5.1 用for创建多个有规律的控件

有时候,要创建多个外观一致或者有规律的控件,一个一个写代码或者复制粘贴的话,就不太pythonic了。在Python中,可以使用for来遍历迭代,同样可以使用for来创建多个外观一致或者有规律的控件。

for_1

3.5.2 与lambda表达式组合使用时的问题

除了要创建一样的控件,还要给每个控件添加事件响应的话,每次都写一遍函数定义未免大材小用,更何况同名函数会出现覆盖,让函数名动态变化又没那么简单。这个时候Python的匿名函数——lambda表达式就派上用场了。lambda表达式可以创建语句简单的匿名函数,不必担心函数名重复的情况。比如,在下面的代码中,通过使用lambda表达式,让按钮的点击操作变成弹出一条通知。

不过,事情并没有看上去那么简单,当写完代码开始执行的时候,才发现每个按钮的点击结果都一样,都是弹出内容为8的通知,这是为何?

原来,使用lambda表达式执行的ui.notify(i),因为表达式没有绑定默认值,实际上绑定到了动态的i上,按钮的on_click的定义不是第一时间执行,而是在完成定义之后响应用户的操作。最终,当for完成遍历之后,动态的i已经被赋值为8,因此按钮的响应操作中的i都被统一修改了。为了避免这种情况,需要修改一下lambda表达式,添加一个参数并绑定默认值:

修改之后的lambda i=i:ui.notify(i)中,i=i的意思是lambda表达式里的i变成了函数的参数i,而这个i绑定到了外部的i当时值。

当然,实际代码中不建议这样写,太容易混淆了(怕被裁员倒是可以这样做)。

上面的代码可以再修改一下,让可读性变得更好:

3.5.3 更好的for循环

为了确保批量生成之后还能访问每个控件,最好将批量生成的控件存储到列表里(不建议使用元组,没法修改;字典非必要也别用,字典的结构有点复杂,除非是列表没法实现需求)。

以下面的代码为例,使用buttons创建一个列表,在列表中用列表生成式来创建多个控件。后续如果需要修改某一个控件,就可以通过buttons来访问任意一个控件,这里是将第一个按钮隐藏。

for_2

3.6 绑定的技巧

3.6.1 绑定到字典

在入门基础里提到的绑定只介绍如何绑定两个控件,其实,绑定除了绑定另一个控件,还支持绑定字典。绑定控件时,target_object是控件对象,这里则换成字典对象;target_name是控件对象的属性名,这里则换成字典的key,于是,就有了以下代码:

binding_1

要注意的是,ui.number的值输出为小数,如果不增加forward=lambda x:int(x)的话,data['age']会被修改为小数,而不是整数。同理,ui.input的值输出为字符串,如果字典输入不是字符串的话,在输出时需要转换。

3.6.2 绑定到全局变量

还是上一节的代码,假如有人说:“字典还是有点复杂,能不能绑定到一个简单的变量上?”

怎么办?

也就是说,data字典没有了,取而代之的是:

其实也简单,只要将data换成globals()即可:

任何在Python文件内定义的全局变量,都会成为全局变量字典的一个键值,可以使用globals()访问全局变量字典。

3.6.3 性能优化

在NiceGUI中有两种类型的绑定:

  1. "Bindable properties" (可绑定属性)会自动检测写入访问并触发值变动传播。大多数 NiceGUI 元素使用这种可绑定属性,例如ui.inputvalueui.labeltext。基本上所有带有bind()方法的属性都支持这种类型的绑定。

  2. 另一种绑定"active links"(活动链接)不会自动检测写入访问并触发值变动传播。如果将标签文本绑定到字典或自定义数据模型的属性,NiceGUI 的绑定模块则需要主动检查值是否发生变化。这个主动检查是通过每 0.1 秒运行一次refresh_loop()来完成。主动检查间隔可以通过设置ui.run()的参数binding_refresh_interval来修改。

可绑定属性非常高效,只要值不变,就不会产生任何性能开销(相对而言比较小而已)。但活动链接需要每秒检查所有绑定值10 次。这可能会消耗比较多的性能,尤其是活动链接的绑定关系非常复杂、非常多的时候。

因为不能让主线程阻塞太久,所以如果太多主动检查导致运行refresh_loop()的耗时过长,程序会发出警告。当然,可以配置阈值binding.MAX_PROPAGATION_TIME(默认为 0.01 秒)来消除警告。但是,这个警告是有意义的,是在告诉开发者性能可能存在问题。比如,CPU在更新绑定花费太长时间的话,主线程就没法做别的事情,程序界面会因此卡住。

为了避免性能出问题,需要将活动链接改为可绑定属性之间的绑定,需要使用binding.BindableProperty()来创建可绑定属性。于是,基于第一小节的代码,将字典改为数据类,在数据类中定义两个可绑定属性,控件的绑定改为与数据类对象的绑定。代码如下:

因为代码中的绑定数量很少,因此差异不大,如果将绑定数量放大百倍,就能看出两种绑定的性能差异。

3.7 app.storage的技巧

有时候,网页上不同页面、用户需要存储、共享特定数据,依靠自己编程实现的话确实麻烦。好在NiceGUI提供了一种简单有效的数据存储功能,那就是app.storage(存储)。 存储有5个子字典,分别对应着不同的空间,有不同的应用范围:

如果因为上述介绍看起来不够直观,而在选用存储字典时候头疼,可以参考下面的对比表格快速选用(✅表示是,❌表示否):

存储的子字典tabclientusergeneralbrowser
存储位置服务器内存服务器内存服务器磁盘服务器磁盘浏览器
是否在不同选项卡之间共享
是否在不同浏览器客户端之间共享
是否在服务器重启后保留数据
是否在页面重载后保留数据
是否只能用在ui.page内
是否需要客户端建立连接
是否只能在响应之前写入
是否要求数据可序列化
是否需要设置storage_secret参数

下面是个使用存储字典的简单例子:

默认数据是以无缩进的JSON格式存储在app.storage.userapp.storage.general中,可以将app.storage.user.indentapp.storage.general.indent设置为True来让对应存储字典的数据采用2个空格的缩进格式。

3.8 修改指定元素的技巧

在CSS中,有个非常重要的概念叫选择器。

每一条css样式定义由两部分组成,形式如下:

{之前的部分就是“选择器”。 “选择器”指明了{样式}中的“样式”的作用对象,也就是“样式”作用于网页中的哪些元素。

选择器有一套自己的语法规则,通过合理设置选择器的规则,可以很精准地选择指定元素。

NiceGUI简化了不少CSS上的操作,但不代表不需要CSS的基础。如果读者掌握了CSS的选择器,与ui.queryui.teleport结合使用,那就如同得到了屠龙宝刀,操作界面布局、美化界面将更加得心应手。

注意,前两小节要求读者具备CSS选择器基础,没有相应基础的读者可以搁置前两小节,直接看第三小节。

3.8.1 ui.query

前面讲过如何美化控件,即在控件定义时使用propsclassesstyle等方法美化控件,也可以在控件定义好之后,通过给定的变量名调用相应方法。但是,如果想要美化的控件、元素根本就不是定义出来的,而是框架带出来的,想要美化就有点麻烦。当然,直接修改内置样式、源码很直观,但麻烦。要是有种方法能让想要修改的内容就像被定义为变量一样,后续直接使用,那就方便不少。正巧,ui.query就有这样的功能。

注意,ui.queryprops方法修改的是HTML元素的属性(attribute),而不是ui.element或者Quasar组件的属性(props)。

ui.query只有一个字符串类型参数selector,顾名思义,就是前面提到的选择器。通过给ui.query传入选择器语法,ui.query将返回CSS选择器能够选择的元素,后续可以直接对该元素执行样式美化的方法。

下面的代码就是使用ui.query选择了body(网页的主体),并设置body的背景颜色:

ui_query

ui.query的用法很简单,难点在于确定CSS选择器的写法,这一部分属于CSS基础知识,这里就不再赘述,有能力的读者可以抽时间深入学习CSS选择器的语法。

3.8.2 ui.teleport

肯定有读者在学了ui.query美化指定元素之后,突发奇想,想要给指定元素内部添加控件,比如,下面的代码:

然而,这段代码并不能成功运行,因为ui.query并不支持add_slot。如果想要实现类似效果,只需将ui.query换成ui.teleport即可,不过传递的参数名不是selector,而是to

ui_teleport

ui.teleport就是这样一个基于CSS选择器语法将任意控件传送至指定位置的控件。

3.8.3 ElementFilter

暂时不会CSS选择器语法的读者也不用着急,尽管CSS选择器语法很强大,但在Python中不够直观,想要快速确定选择器还要去网页中开启调试模式。好在NiceGUI提供了另一种不需要CSS选择器的定位指定元素工具,那就是ElementFilter

ElementFilterui模块同级,使用from nicegui import ElementFilter来导入。

ElementFilter的功能等于ui.queryui.teleport,既能设置指定元素的样式,又能将控件传送到指定位置。但与ui.queryui.teleport使用CSS选择器语法不同,ElementFilter的筛选方式更pythonic,更直观,更契合Python编程习惯。

以下代码是用于匹配的模板内容,以下面的代码为例,分别看看ElementFilter不同参数、方法的用途:

3.8.3.1 初始化方法

ElementFilter是一个类,需要初始化为对象实例才能使用。ElementFilter的初始化方法有四个参数,分别是 kindmarkercontentlocal_scope

kind参数,NiceGUI的ui类型,表示筛选什么类型的控件。比如,在下面的代码中,传入的参数是ui.labelElementFilter就会筛选ui.label,这样给ElementFilter对象设置背景颜色为红色的时候,页面内所有的ui.label的背景颜色就相应变成红色。

ElementFilter_01

marker参数,字符串类型或者字符串列表类型,表示筛选包含指定marker或者指定marker列表的对象。

在此,需要额外介绍一下控件的mark方法,也就是如何给控件添加marker。对于每一个控件,都可以通过mark方法定义一组marker,用于ElementFilter的筛选。mark方法的参数是一个支持解包、分解的字符串类型参数markers。也就是说,传入'A''A','B','AB''B A BA''A','B BA'都是可以的。本质上说,mark方法就是将传入的字符串转换为该对象的_markers列表。对于'A','B','AB'这样多个字符串,该方法会转化为['B','A','AB']这样的列表来使用。对于'B A BA'这样用空格划分的字符串,该方法会自动以空格为分隔符分解为['B','A','BA']这样的列表来使用。当然,两种方法混用也没问题,'A','B BA'这样的多个字符串,则会转化为['A','B','BA']这样的列表。注意,虽然mark方法支持串联、重复使用,但最好不要这样做,因为后执行的mark方法结果会覆盖先前mark方法的结果,如果是想清除之前的marker,倒是可以重复执行。

说完给控件添加marker,下面回归正题,说说如何筛选。marker参数和mark方法的markers参数类似,只不过marker参数没有解包过程,想要传入多个字符串,只能使用字符串列表。与mark方法的宽松不同,marker参数的要求比较严格,要么是纯字符串,带空格的会自动划分、转化为列表,要么是无空格的字符串组成列表,不支持正确解析内含带空格的字符串列表,所以,只有以下格式才是正确的用法:'A'['A','B','AB']'B A BA'

代码示例如下:

ElementFilter_02

content参数,字符串类型或者字符串列表类型,表示筛选包含指定内容的对象。筛选范围包括对象的valuetextlabeliconplaceholder等文本属性。匹配要求完全包含指定字符串或者字符串列表。

ElementFilter_03

local_scope参数,布尔类型,表示ElementFilter匹配当前范围还是全局,默认为False,即匹配全局。如果设置为True,则只匹配当前上下文。可以看以下代码,修改了缩进并将此参数设置为True,ElementFilter对象就只能匹配同一缩进内的控件:

ElementFilter_04

3.8.3.2 within方法和not_within方法

顾名思义,这两个方法就是在ElementFilter初始化参数的筛选范围内进一步筛选指定的父级对象,得到在指定的父级对象上下文之内、不在指定的父级对象上下文之内的对象。对within方法而言,会得到符合该方法匹配条件的对象。对not_within方法而言,会排除符合该方法匹配条件的对象

两个方法的参数都一样,都是三个,分别是kindmarkerinstance

kindmarker与初始化方法的参数一样,这里不再赘述。只是,这里的marker不支持字符串列表。

instance参数,对象或者对象列表,指定具体对象的范围内是否筛选。以 within方法为例,给此参数传递具体对象,ElementFilter将只筛选在该对象之内的ui.label

ElementFilter_05

这两个方法支持串联调用,不过串联就和传递列表给参数一样,是扩展了对应筛选条件的内部列表。对于这两种筛选条件的内部列表,匹配规则是不一样的:对于within方法,筛选则是要求列表内元素全部匹配;对于not_within方法,筛选则是要求列表内元素任意一个匹配。

3.8.3.3 exclude方法

该方法是在ElementFilter初始化参数的筛选范围内进一步排除指定的对象。

该方法有三个参数,kindmarkercontent ,同初始化方法的参数一样,这里简单说一下示例代码,不做详解。不过,该方法的三个参数不支持传入列表,marker也不支持根据空格自动划分字符串,这一点需要注意。

ElementFilter_06

ui.labelui.button都继承了TextElement,因此匹配TextElement会同时匹配到这两种控件,因此,在exclude方法中指定kindui.label之后,匹配结果就排除了ui.label,只有ui.button的颜色变成红色。

3.8.3.4 传送控件到匹配结果

对于ElementFilter,想要传送控件到结果也很简单,只需遍历ElementFilter对象,就能获取匹配结果。

如下面代码所示,使用for遍历ElementFilter对象,使用with进入每个元素的上下文,就和正常添加控件到对应slot一样:

ElementFilter_07

3.8.3.5 总结

ElementFilter的方法、参数不多,但用法不统一,要是组合使用,需要一些时间思考其匹配模式。而有的读者看到文字太多就头疼,没关系,这里将上面的内容简化为一个表格方便查阅。详细看过一遍文字教程之后,后续开发中再次遇到,可以快速参阅表格来确定匹配模式。

对应参数的匹配模式:

ElementFilter的方法__init__withinnot_withinexclude
kind参数任意一个全部匹配任意一个任意一个
content参数全部匹配无此参数无此参数任意一个
instance参数无此参数全部匹配任意一个无此参数
marker参数全部匹配全部匹配任意一个任意一个

Match type for parameters in ElementFilter's method:

ElementFilter's method__init__withinnot_withinexclude
parameter kindany/orall/andany/orany/or
parameter contentall/and--------any/or
parameter instance----all/andany/or----
parameter markerall/andall/andany/orany/or

另外,对于NiceGUI2.1版本的ElementFilter部分方法参数不支持列表传入,这里特地补丁了一份模块文件,有需要的读者可以自行替换,文件的具体路径为.venv\Lib\site-packages\nicegui\element_filter.py,如果是全局环境的Python,路径为{Python执可执行文件所在目录}\Lib\site-packages\nicegui\element_filter.py

3.9 其他布局

前面介绍过常用的布局,其实NiceGUI支持的布局控件有很多,下面提到的这些不常用,但有些需求比较刁钻,用这些布局刚好可以减少不必要的工作。

3.9.1 ui.list

列表布局,看上去有点像ui.column,但列表布局主要是给ui.item用的,当然,要是想在一般布局的时候使用也没问题,不过需要注意一下二者的区别。

以代码为例:

ui_list

为了能清楚看到二者的区别,这里给两种布局加了个边框,方便看到边界。可以看出,ui.listui.column的默认行距小,整体看上去更加紧凑。当然,ui.list主要是给ui.item用的,默认的紧凑是有别的用途,如果在一般布局中需要紧凑一些的观感,可以根据需求调整样式,而不是使用ui.list代替ui.column

关于ui.list更恰当的用途,还是以代码为例:

ui_list2

ui.item内增加ui.item_sectionui.item_label等控件,可以实现类似通讯录的布局。不同于使用基本布局组合实现需要较多的样式调整,这些预定义的控件本身有很多属性样式,只需设置好对应的属性,就能让样式接近想要的效果。而且,本身的含义也比较直观,后续维护起来也方便。

3.9.2 ui.splitter

前面介绍过ui.separator,可以生成一条水平或者垂直的分隔线,用来不明显地区分控件。但是,在NiceGUI中,除了ui.separator外,还有一种分隔线,那就是ui.splitter,也可以生成一条水平或者垂直的分隔线。不过,在前面没介绍这个控件,是因为这个控件的用法可比ui.separator复杂,用途也多,只是简单分隔一下,还是用ui.separator比较好。倘若对于分隔有更高的要求或者其他用途,ui.splitter绝对能胜任。

先看代码,分别用ui.separatorui.splitter实现类似的效果:

ui_splitter

单看效果对比的话,ui.splitter看上去只是比ui.separator紧凑一些,似乎没什么不同。如果仔细看代码,就会发现ui.splitter的代码量比ui.separator多不少。

ui.splitter可以看做是一个容器,定义ui.splitter之后,会产生ui.splitter().beforeui.splitter().afterui.splitter().separator三个子容器,需要分别给前两个容器填充内容,才能看到分隔效果。而且,中间的分隔线也支持添加内容和鼠标交互,可以用鼠标左右拖动。

当然,ui.splitter支持的参数不少,用起来也比ui.separator复杂:

horizontal参数,布尔类型,是否将ui.splitter水平显示。

limits参数,浮点元组类型,表示拖动分隔线的范围(最小、最大百分比),这是一个二元元组,默认为(0,100),第一个元素表示拖动范围的最小值,,第二个元素表示拖动范围的最大值。

value参数,浮点类型,表示ui.splitter().before的初始大小百分比。

reverse参数,布尔类型,表示是否反转ui.splitter().beforeui.splitter().after先后顺序。

on_change参数,可调用类型,表示拖动分隔线时执行的操作。

以下代码是ui.splitter的典型运用,通过拖动分隔线,显示彩色、黑白图像的对比效果,同时弹出通知显示当前分隔线的位置:

ui_splitter2

3.9.3 ui.tabs

选项卡,现代浏览器的基本布局,可以通过点击上面的选项卡,切换下面的内容。

在NiceGUI中,和选项卡相关的控件有ui.tabsui.tabui.tab_panelsui.tab_panelui.tabs是放置选项卡的容器,ui.tab是选项卡,ui.tab_panels是放置选项卡关联内容的容器,ui.tab_panel则是选项卡的关联内容。

以下面的代码为例,ui.tab放置在ui.tabs内,ui.tab_panel放置在ui.tab_panels内,想要让选项卡正确关联、交互,需要确保以下参数正确设置:

  1. ui.tab_panels的参数tabs需要传递已经创建的ui.tabs实例;

  2. ui.tab_panel的参数name需要传递已经创建的ui.tab实例,或者实例的字符串参数name

ui_tab

ui.tabname参数是用来区分选项卡的唯一标识符,称之为id也可以,但是,肯定有读者觉得不太方便,如果想要修改选项卡的名字,势必影响到下面ui.tab_panel的关联。其实,并不会导致这样的问题,ui.tab还有一个字符串参数label,如果设置了这个参数,显示在选项卡上的内容就会变成label而不是name。此外,ui.tab还支持像ui.button一样设置图标,只需给参数icon传入图标字符的名字即可:

ui_tab2

除了通过点击交互来切换选项卡,调用ui.tabsui.panelsset_value方法也能切换选项卡:

想要让选项卡从水平变成垂直,只需调用props('vertical'),设置'vertical'即可。不过,只是设置一下,界面并不会如预想中那样改变,还需要借用前面介绍到的ui.splitter

ui_tab3

ui.tabs支持两个参数:

value参数,字符串类型或者ui.tab类型或者ui.tab_panel类型,用于指定初始化选择的ui.tab或者ui.tab_panel,如果是字符串类型,则值为ui.tab或者ui.tab_panelname属性的值。但是,因为定义ui.tabs时,ui.tabui.tab_panel通常还没定义,这个参数一般不需要知道,也没法指定。

on_change参数,可调用类型,当选项卡切换时执行的操作。

ui.tab支持三个参数:

name参数,字符串类型,表示选项卡的名字,也可以称之为标识符,如果对应的ui.tab_panel也定义了name,需要保持一致。

label参数,字符串类型,表示选项卡的标签,如果此参数没有定义,默认使用name的值。

icon参数,字符串类型,表示选项卡的图标。

ui.tab_panels支持五个参数:

tabs参数,ui.tabs类型,与已经创建的ui.tabs关联,ui.tabs切换选项卡时,ui.tab_panels也切换为相应的选项卡。

value参数,字符串类型或者ui.tab类型或者ui.tab_panel类型,用于指定初始化选择的ui.tab或者ui.tab_panel,如果是字符串类型,则值为ui.tab或者ui.tab_panelname属性的值。但是,因为定义ui.tab_panels时,ui.tab_panel通常还没定义,这个参数一般不设置为ui.tab_panel类型对象,而是指定为字符串或者已经定义的ui.tab

on_change参数,可调用类型,当选项卡切换时执行的操作。

animated参数,布尔类型,表示是否启用切换动画,默认为True

keep_alive参数,布尔类型,表示是否对容器内的控件启用VUE的keep-alive组件。启用此组件,容器内控件不会在不可见的时候销毁,而是一直保持存活,以免再次访问控件时,控件的状态因为重建而被重置。

ui.tab_panel支持一个字符串参数name,与ui.tabname相同,如果值一致,则会将ui.tab_panelui.tab关联。

3.9.4 ui.scroll_area

对于较多的内容放置在网页,会导致网页又臭又长,可以使用滚动区域当容器。滚动区域控件会生成滚动条,让内容只在指定的大小内显示,拖动滚动条可以显示其余内容。

滚动区域控件支持一个可调用类型参数on_scroll作为响应滚动的执行操作,通过一个事件参数捕获滚动的响应事件,并将vertical_position(当前位置的垂直位置,单位像素)、vertical_percentage(当前位置的垂直位置,单位百分比)、vertical_size(滚动内容的垂直大小,单位像素)、vertical_container_size(滚动区域容器的垂直大小,单位像素)、horizontal_position(当前位置的水平位置,单位像素)、horizontal_percentage(当前位置的水平位置,单位百分比)、horizontal_size(滚动内容的水平大小,单位像素)、horizontal_container_size(滚动区域容器的水平大小,单位像素)等属性传递出来。此外,该控件的scroll_to方法可以设置控件内容滚动到什么位置。

以下是简单的示例:

ui_scroll_area

on_scroll参数传入带一个参数的lambda表达式,可以捕获滚动的响应事件,该事件的属性均为浮点类型。以下面的代码为例,代码中捕获的该事件的vertical_percentage,并将其赋给ui.number

ui_scroll_area2

scroll_to方法可以设置控件内容滚动到什么位置,该方法有四个参数:

pixels参数,浮点类型,用像素表示目标位置,不能与percent参数同时指定。

percent参数,浮点类型,用百分比表示目标位置,不能与pixels参数同时指定。

axis参数,字符串类型,限定为'vertical''horizontal',默认为'vertical',表示滚动的方向是水平还垂直。

duration参数,浮点类型,表示滚动动画的持续时间,默认是0,表示启用滚动动画。

以下代码在左边滚动区域的响应事件中添加执行了右边滚动区域的scroll_to方法,让左右的滚动保持一致:

ui_scroll_area3

3.9.5 ui.skeleton

骨架控件可以提供一系列占位轮廓,当内容还没有加载的时候,用占位轮廓提供网页的内容结构预览。同时,骨架控件上的鼠标样式会显示为正忙的样式,表示内容正在加载。

以下为代码示例:

ui_skeleton

type参数,字符串类型,表示骨架的基本形状,支持'text''rect''circle''QBtn''QBadge''QChip''QToolbar''QCheckbox''QRadio''QToggle''QSlider''QRange''QInput''QAvatar',默认为'rect'

tag参数,字符串类型,表示创建骨架控件用的HTML标签,默认为'div'

animation参数,字符串类型,骨架控件的动画,因为是在加载过程中占位显示,必须要有动画,以缓解用户等待期间的焦虑。支持'wave''pulse''pulse-x''pulse-y''fade''blink''none',默认为'wave'

animation_speed参数,浮点类型,表示动画的速度,即在多少秒完成一次动画循环,默认为1.5

square参数,布尔类型,表示是否移除骨架控件的圆角,默认为False

bordered参数,布尔类型,表示是否显示骨架控件的边框,默认为False

size参数,字符串类型,表示使用CSS的大小单位指定骨架控件的大小,此时骨架控件显示为正方形或者圆形(取决于type参数),并且会覆盖width参数和height参数的设置,默认为None

width参数,字符串类型,表示使用CSS的大小单位指定骨架控件的宽度,会被size参数覆盖,默认为None

height参数,字符串类型,表示使用CSS的大小单位指定骨架控件的高度,会被size参数覆盖,默认为None

3.9.6 ui.carousel

‌‌轮播图是一种常见的网页设计元素,主要用于提供网页内容的快速展示和导航。‌ 它通过切换多个图片或内容,吸引用户的注意力,提高页面的视觉吸引力。轮播图支持自定义轮播图片、轮播动画效果等,能够在可视化应用中展示多张图片轮流播放的效果。

在NiceGUI中,ui.carousel就是可以实现轮播图效果的控件,不过它的英文是carousel,翻译过来的话是旋转木马,听起来不太像控件,这里就用轮播图代替。

ui_carousel_sketch

NiceGUI的轮播图控件,本质上是一种容器,轮播图会依次展示每个子控件,放在其中的子控件就是像一页一页的幻灯片。一般来说,轮播图子控件应该是ui.carousel_slide,实际上用其他控件也可以。

ui.carousel有五个参数:

value参数,字符串类型或者ui.carousel_slide类型,表示轮播图初始展示哪一个子控件,默认为None,表示展示第一个。

on_value_change参数,可调用类型,表示轮播图当前展示的子控件变化时,执行什么操作。

animated参数,布尔类型,表示是否开启切换动画。

arrows参数,布尔类型,表示是否显示手动切换上、下一个子控件的箭头。

navigation参数,布尔类型,表示是否显示下面直接切换哪一个子控件的圆点。

previous方法,切换上一个子控件。

next方法,切换下一个子控件。

ui.carousel_slide只有一个字符串参数name,也就是上面ui.carouselvalue用到的指定当前页的值。默认没有指定的话,这个值是自动生成的——'slide_1'这种名字,下划线后的数字代表当前控件在所有子控件中的排序。

ui_carousel

3.9.7 ui.expansion

在其他UI框架中可能叫做Accordion或者手风琴,这里也称之为手风琴控件,而不是直译其为扩大。

‌手风琴控件是一个用来展示多个面板的控件,这些面板同时只能展开一项(也可以一项都不展开),和选项卡控件有异曲同工之妙。‌手风琴控件允许在一个紧凑的空间中显示许多链接,通过单击或点击来展开和折叠,使得用户可以在一个可视化的界面中快速访问不同的信息或功能。这种控件通常包括一个或多个可折叠的面板,每个面板可以包含文本、图像或其他内容,用户可以通过点击面板的标题来展开或折叠对应的内容。手风琴控件的设计旨在提供一个直观且用户友好的界面,使用户能够轻松地在不同部分之间切换,同时保持界面的整洁和有序。

先看代码和效果图:

ui_expansion

ui.expansion有六个参数:

text参数,字符串类型,表示手风琴控件的文本内容。

caption参数,字符串类型,表示手风琴控件的说明性文本内容(也可以称之为副标题或者标签文本,比文本内容小一点)。

icon参数,字符串类型,表示手风琴控件的图标。

group参数,字符串类型,表示手风琴控件的分组,默认为None。其实,单个ui.expansion没法组成手风琴控件,需要多个配合。在没有指定此参数的情况下,每个ui.expansion都是独立打开、关闭的,只有在指定相同的此参数之后,各个ui.expansion的开闭才会关联,每组ui.expansion中只允许一个ui.expansion开启,点开其他ui.expansion会让已经打开的ui.expansion关闭。

value参数,布尔类型,表示ui.expansion的开关状态,默认为False

on_value_change参数,可调用类型,表示手风琴控件的值变化时执行什么操作。

open方法,调用此方法会打开手风琴控件。

close方法,调用此方法会关闭手风琴控件。

如果想用图片代替手风琴控件的图标,可以这样操作:

ui_expansion2

前面提到的group参数,用法也很简单:

ui_expansion3

3.9.8 ui.pagination

常在网页中看到多页内容的底部有标着页码的分页控件,点击后一页或者对应页码可以直接跳转。在NiceGUI,实现此功能的是ui.pagination

ui_pagination

ui.pagination有五个参数:

min参数,整数类型,分页控件的页码最小值。

max参数,整数类型,分页控件的页码最小值。

direction_links参数,布尔类型,是否显示前一页、后一页的链接。

value参数,整数类型,分页控件的页码初始值。如果没有指定初始值,初始值是最小值。

on_change参数,可调用类型,表示分页控件的值变化时执行什么操作。

分页控件的更多样式设计可以参考Quasar官网

ui_pagination2

3.9.9 ui.stepper

ui.stepper是一个类似向导功能的综合控件,可以给用户提供直观的操作引导。从本质上说,ui.stepper是个容器,有点像前面提到的轮播图,每次都是展示当前子控件的内容。不同的是,ui.stepper会在上方(或者左边)提供全部子控件标题作为步骤的预览,大致结构如下图所示:

ui_stepper_sketch

ui.stepper相关的控件有ui.stepui.stepper_navigationui.step是每一步的步骤,放在ui.stepper的上下文内。ui.step的上下文主要放置步骤的操作说明,这里的内容会展示在步骤预览的下方。ui.stepper_navigation是导航栏,里面放置下一步、上一步之类的操作按钮,通常放置在ui.step内,也可以放置在外面。

顺着上面的思路,代码如下:

ui_stepper

ui.stepper支持三个参数:

value参数,字符串类型或者ui.step类型,表示初始选择的步骤是哪一个,默认为None,即第一个。因为步骤的定义在这一行后面,直接使用ui.step类型会触发未定义报错,要指定非第一个当初始选择步骤,一般只能用字符串类型的名字,即ui.stepname属性的值。实际上ui.stepper点击下一步的操作,也就是将ui.steppervalue设置为指定ui.stepname的值。

on_value_change参数,可调用类型,当ui.steppervalue变化时执行的操作。

keep_alive参数,布尔类型,表示是否对容器内的控件启用VUE的keep-alive组件。启用此组件,容器内控件不会在不可见的时候销毁,而是一直保持存活,以免再次访问控件时,控件的状态因为重建而被重置。

previous方法,切换上一步骤。

next方法,切换下一步骤。

ui.step支持三个参数:

name参数,字符串类型,和ui.tab类似,name也是ui.step的唯一标识符。同时,当title没有被定义时,这也是title的默认值。

title参数,字符串类型,表示步骤的标题,当此参数没有定义、为默认的None时,此参数会取name的值。

icon参数,字符串类型,表示步骤的默认图标。注意,此图标只有在当前步骤没有被选定或者完成的时候显示。

ui.stepper_navigation支持一个布尔类型参数wrap,表示里面的内容如果超出容器宽度的话,是否自动换行,默认为True

ui.stepper除了横向显示步骤,还可以变成竖向显示,只需调用props('vertical'),设置'vertical'即可:

ui_stepper2

3.9.10 ui.timeline

时间线控件有点像ui.stepper,不同的是,时间线控件只是展示为主的控件,并不具备交互功能。时间线控件可以像绘制思维导图一样,提供一个按照时间排序展示内容的控件。

以代码为例,ui.timeline需要内嵌ui.timeline_entry,才能构成完整的时间线控件:

ui_timeline

ui.timeline支持三个参数:

side参数,字符串类型,表示时间线的内容在时间线的哪边,允许值是'left''right',默认是'left',即左边。

layout参数,字符串类型,表示时间线的布局,允许值是'dense''comfortable''loose',默认是'dense'。dense布局是时间点的大字标题、标题、副标题和主要内容在时间线指定的side一边。comfortable布局是时间点的大字标题、标题和主要内容在时间线指定的side一边,副标题在另一边。loose布局是大字标题在中间,标题和主要内容在时间点指定的side一边,副标题在另一边。三种布局的对比如下:

ui_timeline2

color参数,字符串类型,表示时间线的颜色。

ui.timeline_entry支持的控件比较多,有九个:

body参数,字符串类型,表示时间点的主要内容。

side参数,字符串类型,表示时间线的内容在时间线的哪边,允许值是'left''right',默认是'left',即左边。注意,ui.timeline_entryside只有在ui.timelinelayout属性为'loose'时生效。

heading参数,布尔类型,表示时间点是否为大字标题,默认为False。如果为True,当前时间点将只显示主要内容,并且样式与其他时间点不同,具体参考上面的布局对比图片。

tag参数,字符串类型,表示当前时间点如果被设定为大字标题,使用什么HTML标签当做大字标题的外围标签,默认是'h3'

icon参数,字符串类型,表示当前时间点的图标。

avatar参数,字符串类型,表示当前时间点的头像,可以用图片的地址。注意,如果指定了icon参数,则只会显示图标,因为图标的优先级比头像高。

title参数,字符串类型,表示当前时间点的标题。

subtitle参数,字符串类型,表示当前时间点的副标题。

color参数,字符串类型,表示时间点的颜色,如果当前时间点没有指定颜色,则采用时间线的颜色。

3.9.11 ui.notification

ui.notification也是通知提示控件,相比ui.notifyui.notification可以在显示的同时更新内容和一些其他属性,还可以使用dismiss方法手动移除。

而且,ui.notification的参数也不藏着掖着,直接放在提示里:

message参数,字符串类型,信息文本,显示在通知中的主要内容。

position参数,字符串类型,通知出现的位置,有"top-left""top-right""bottom-left""bottom-right""top""bottom""left""right""center"可选,默认为"bottom"

close_button参数,字符串类型或者布尔型,是否显示关闭按钮,如果是字符串类型,关闭按钮的文字就是给定的文字,默认为False

type参数,字符串类型,通知的类型,有"positive""negative""warning""info""ongoing",默认为None,不是其中的任何一种。

color参数,字符串类型,通知的背景颜色。

multi_line参数,布尔类型,是否让通知内容以多行格式显示。

icon参数,字符串类型,通知的图标,默认为None

spinner参数,布尔类型,是否显示转盘动,默认为False

timeout参数,整数型,通知自动消失的时间,单位秒,默认为5,为0就是不消失。但是要确保close_button不是False,或者有调用dismiss的方法,否则通知没法正常消除,影响用户体验。

on_dismiss参数,可调用类型,当通知消失时执行的操作。

options参数,字典类型,其他Quasar的通知控件API中可用的参数可以传入此字典。

由于ui.notification的特殊性,现在可以弹出一个显示百分比进度并自动消失的通知了(并不需要估计执行时间):

ui_notification

3.9.12 ui.dialog

对话框常见于桌面程序,在网页中并不多见。因为网页经常通过页面切换来跳转到特定界面,实现对话框一样的功能。但是,NiceGUI是混合框架,网页中也没法完全不用对话框。所以,还是有必要看一下对话框的用法。

ui.dialog只有一个布尔类型参数value,表示对话框初始的开启状态,默认为False。其余的对话框设计样式可以参考Quasar的文档

以下是示例:

ui_dialog

调用对话框的open方法打开对话框,调用close方法、点击关闭按钮、点击空白处、按esc键都可以关闭对话框。如果想设置为只有点击关闭按钮才能关闭对话框,可以使用 .props('persistent')添加属性'persistent'

需要注意的是,对话框实际运行时只创建一次,后续是重复使用的。关闭对话框并不会销毁对话框,只是隐藏对话框,而且下次开启时会重复使用已经创建的对话框。如果想要确保对话框内容准确,要么在打开对话框前更新对话框内容,要么每次打开前重新创建一次。更新对话框内容有两种方法,一是遍历对话框内每个控件,调用对应的更新方法;二是重新创建对话框内的内容,使用clear方法清除之后重新创建,或者使用refreshable方法包装需要更新的控件,调用refresh方法触发刷新重建。

以下面的代码为例,为了更新对话框的内容,先调用clear方法清除原有内容,然后创建新内容:

除了在对话框里修改全局变量来传递用户选择的结果,对话框还可以通过异步等待的方式返回结果,代码如下:

3.9.13 ui.menu补充

ui.menu中除了可以嵌入ui.menu_item,还可以嵌入其他控件,有时候会有意想不到的效果:

ui_menu_2

3.9.14 ui.tooltip补充

对于像ui.htmlui.markdownui.upload等不支持添加tooltip的元素,可以使用ui.element包装来间接实现:

tooltip里除了显示一般的文本,还可以显示图像等其他内容。不过,不建议在tooltip内放置需要交互的内容,因为被添加tooltip的控件一旦失去焦点,tooltip就会消失,里面的交互内容永远无法交互:

ui_tooltip_2

3.10 其他常用控件

3.10.1 ui.dropdown_button

前面在介绍菜单控件的时候,是用按钮内嵌入菜单,实现点击按钮弹出菜单的效果。其实,在NiceGUI中,有个控件可以实现一步到位,无需嵌入,那就是ui.dropdown_button——下拉按钮。

先看示例:

ui_dropdown_button

可以看到,下拉按钮的弹出效果很像在按钮中嵌入了菜单,但需要的代码更少,结构也更清晰。当然,样式上因为多了一个下拉的三角,没法像嵌入菜单那样灵活,读者可以根据需求自主选择。

下拉按钮支持以下参数:

text参数,字符串类型,表示显示在按钮上的文字,和普通按钮一样。

value参数,布尔类型,表示下拉按钮的初始状态是否为下拉内容弹出,默认为False

on_value_change参数,可调用类型,表示下拉按钮的value变化(即下拉内容弹出状态变化)时执行什么操作。

on_click参数,可调用类型,表示点击下拉按钮主体(不是右边的小三角)时执行什么操作,和普通按钮一样。

color参数,字符串类型或者None,表示按钮的颜色,支持传入字符串类型的颜色类(Quasar、 Tailwind、CSS的颜色名)或者None(即让按钮变成默认颜色),默认为'primary',即和主题颜色一致。

icon参数,字符串类型,表示按钮额外显示的图标,支持传入字符串类型的图标名,和普通按钮一样。

auto_close参数,布尔类型,表示点击下拉内容之后是否自动关闭下拉内容的弹出,就和菜单项一样,默认为False

split参数,布尔类型,表示是否显示分隔符来区分按钮主体和下拉弹出响应区,默认为False

同样的,下拉按钮也可以嵌入其他控件,实现想要的效果:

ui_dropdown_button2

想要修改下拉触发区的图标,可以参考API文档,修改'dropdown-icon'属性(props),

ui_dropdown_button3

3.10.2 ui.button_group

下拉按钮看起来就像两个按钮组合到一起,也可以用按钮组控件模拟实现,让右边的按钮支持更多功能:

ui_button_group

下拉按钮也是按钮,一样可以和按钮组组合使用:

ui_button_group2

3.10.3 ui.badge

角标控件,可以在一个控件的上层显示简单的文字,就像手机图标上提示有多少消息未读的角标一样:

ui_badge

角标控件支持以下参数:

text参数,字符串类型,显示在角标内的文字。

color参数,字符串类型或者None,表示角标的颜色,支持传入字符串类型的颜色类(Quasar、 Tailwind、CSS的颜色名)或者None(即让角标变成默认颜色),默认为'primary',即和主题颜色一致。

text_color参数,字符串类型或者None,表示文字的颜色,支持传入字符串类型的颜色类(Quasar、 Tailwind、CSS的颜色名)或者None(即让文字变成默认颜色)。

outline参数,布尔类型,是否启用轮廓线风格,默认为False即填充风格。

更多角标的设计属性可以参考API文档,示例代码中的'floating'就是修改了设计属性,让角标显示在右上角。其他有用的属性有'rounded''transparent''label'等。

3.10.4 ui.chip

薄片控件,看上去有点像角标,但交互性远远高于角标。可以点击、选择、移除,设计属性也比角标多。先看示例代码:

ui_chip

薄片控件支持以下参数:

text参数,字符串类型,显示在控件内的内容。

icon参数,字符串类型,控件内的图标。

color参数,字符串类型,表示控件的颜色,支持传入字符串类型的颜色类(Quasar、 Tailwind、CSS的颜色名),默认为'primary',即和主题颜色一致。

text_color参数,字符串类型或者None,表示文字的颜色,支持传入字符串类型的颜色类(Quasar、 Tailwind、CSS的颜色名)或者None(即让文字变成默认颜色)。

on_click参数,可调用类型,当点击控件时执行什么操作。注意,设置此参数,会同时添加控件的'clickable'属性(props),启用控件的鼠标悬停效果,让控件响应'click'事件。也就是说,如果不设置on_click参数或者调用on_click方法来设置点击事件,而是直接使用ui.chip('test').on('click',handler=lambda: ui.notify("Clicked"))设置点击事件响应,这样操作并不能成功,只有ui.chip('test').props('clickable').on('click',handler=lambda: ui.notify("Clicked"))这样同时添加'clickable'属性才行。否则,只能用ui.chip('test').on('mousedown',handler=lambda: ui.notify("Clicked"))这样直接监听鼠标事件的响应才行,但这样会导致控件没有鼠标悬停效果。

selectable参数,布尔类型,控件是否可选择,默认为False

selected参数,布尔类型,控件是否已经被选择,默认为False

on_selection_change参数,可调用类型,当控件被选择时执行什么操作。

removable参数,布尔类型,控件是否可被移除,默认为False。如果设置为True,控件上会多一个"X"移除按钮。

on_value_change参数,可调用类型,当控件的被移除状态变化时执行什么操作。实际上,控件被移除、未被移除,就是将控件的value设置为FalseTrue

更多的设计属性可以参考官方API

以下是一个动态添加、移除控件的代码示例:

3.10.5 ui.radio

单选按钮,每次只能选择其中一个值。

ui_radio

单选按钮的参数很简单:

options参数,列表类型或者字典类型,表示单选按钮的所有选项。如果是列表,每个元素既是当前选择的值,也是显示出来的文本。如果是字典,则键(key)是当前选择的值,值(value)是显示出来的文本。

value参数,表示控件初始选择的值。

on_change参数,可调用类型,当值变化时执行什么操作。

更多设计属性参考官方API

除了让选项显示文本,借用ui.teleport的功能,还能让选项显示图标:

ui_radio2

3.10.6 ui.toggle

切换控件,功能上和单选按钮一样,操作起来有点像老式磁带播放机的按钮,每次只能点选一个:

ui_toggle

切换控件支持以下参数:

options参数,列表类型或者字典类型,表示切换控件的所有选项。如果是列表,每个元素既是当前选择的值,也是显示出来的文本。如果是字典,则键(key)是当前选择的值,值(value)是显示出来的文本。

value参数,表示控件初始选择的值。

on_change参数,可调用类型,当值变化时执行什么操作。

clearable参数,布尔类型,表示是否可以通过点击当前选择的选项来取消选择,默认为False,即默认必须选择一个,没法取消选择。

更多设计属性参考官方API

3.10.7 ui.select

下拉选择框,同样是一个提供多种选择内容的控件,只不过下拉选择框是弹出可选择的内容,在选择之后,将被选择的内容显示在框内。NiceGUI的下拉选择框支持的参数和设计属性比较多,也让这个看起来简单的选择控件有无数可能。在详细学习之前,先看简单的示例:

ui_select

下拉选择框支持以下参数:

options参数,列表类型或者字典类型,表示控件的所有选项。如果是列表,每个元素既是当前选择的值,也是显示出来的文本。如果是字典,则键(key)是当前选择的值,值(value)是显示出来的文本。

label参数,字符串类型,直译的话是标签,表示显示在选择框上方的文本,但不是选择的文本,如果当前选择的内容是空的,点击选择的之前会显示在选择框内,点击之后会移动到选择框上方。

value参数,表示控件初始选择的值。

on_change参数,可调用类型,当值变化时执行什么操作。

with_input参数,布尔类型,表示是否显示一个输入框在选择框内,用输入框内的内容筛选选项,默认为False,即不显示输入框。

new_value_mode参数,字符串类型或None,表示在选择框内输入值而不是选择当前定义好的选项,直接回车的话,执行什么样的操作。这个参数只支持'add''add-unique''toggle'。其中,'add'表示只能添加没有的值,即根据输入的值筛选当前已有的选项,如果有筛选结果,选择第一个,没有筛选结果则添加这个值。'add-unique'表示添加当前值到选项中,哪怕值是相同的,也能添加为新的选项。'toggle'表示没有就添加,和'add-unique'一样的添加;如果有就删除,注意,删除时候需要取消下拉弹出选项的焦点,确保选择框为输入状态,但没有弹出的选项,回车即可删除与当前输入框的内容相同的新增选项。默认为None,表示不会添加新的选项。

multiple参数,布尔类型,表示是否支持多选。

clearable参数,布尔类型,表示是否添加一个清除当前选择的按钮。

validation参数,可调用类型、字典类型或者None,表示验证选择的内容是否有效。如果传入可调用类型参数,该参数返回错误信息表示内容无效,返回None表示内容有效。如果传入字典类型参数,则字典的键(key)表示错误信息,字典的值(value)为可调用类型参数,字典的值(value)返回True表示内容有效,返回False则表示内容有效并输出错误信息。默认值为None,表示不验证选择的内容。

key_generator参数,生成器类型、迭代器类型或者可调用类型,当options为字典类型且new_value_mode不为None时,此参数用于生成字典的键(key)。当此参数为生成器类型和迭代器类型时,每次新添加选项的键(key)就是依次遍历此参数获得,该选项的显示文本就是输入的内容。当此参数不能继续遍历时,新的选项则没法添加。如果此参数为可调用类型,每次新添加选项的键(key)就是将输入内容当做参数、可调用参数返回的执行结果,该选项的显示文本就是输入的内容。注意,当new_value_mode为'add'时,此参数必须正确设置,否则会报错。

更多设计属性参考官方API

使用添加输入框参数,可以同时启用搜索功能:

ui_select

多选功能:

ui_select2

调用set_options方法更新选项:

两种验证格式:

ui_select3

使用生成器和迭代器作为key_generator

注意,如果传入的key_generator是生成器,默认控件会执行一次next方法,然后才开始执行send方法,会导致生成器第一个生成(yield)的值被抛弃,这样做的目的是每一步都能接收到send方法的参数。所以,如果是简易的生成器,不接收send方法的参数,或者不太熟悉生成器语法的话,可以在头部插入任意值用于执行next方法,也可以使用iter方法转换为迭代器来使用。

3.10.8 ui.checkbox

复选框控件,用于显示一个支持勾选的复选框。

ui_checkbox

复选框控件的参数很简单:

text参数,字符串类型,显示在控件右边的文字内容。

value参数,布尔类型,控件初始的勾选状态,默认为False,即不勾选。实际上,勾选复选框也就是将控件的value属性设置为True,调用控件的set_value方法可以改变勾选状态。

on_change参数,可调用类型,当控件的勾选状态变化时执行什么操作。

更多设计属性参考官方API

3.10.9 ui.switch

开关控件,显示一个像开关一样可以切换开启、关闭状态的控件。

ui_switch

开关控件的参数很简单:

text参数,字符串类型,显示在控件右边的文字内容。

value参数,布尔类型,控件初始的开关状态,默认为False,即关闭。实际上,切换为打开状态也就是将控件的value属性设置为True,调用控件的set_value方法可以改变开关状态。

on_change参数,可调用类型,当控件的开关状态变化时执行什么操作。

更多设计属性参考官方API

3.10.10 ui.range

范围条控件有点像之前提到的ui.slider,参数都是一样的,只是value参数略有差异。

ui_range

范围条控件支持以下参数:

min参数,浮点类型,范围条最小值。

max参数,浮点类型,范围条最大值。

step参数,浮点类型,范围条的步长。

value参数,字典类型,范围条当前值。注意,因为范围条有两个滑块,不像ui.slider只有一个,因此,ui.slider值只是一个浮点数,范围条的值是一个字典,{'min': 20, 'max': 80},键(key)是字符串,必须包含'min''max',对应的值(value)就是左右滑块的当前位置。注意,虽然交互过程中,左边的滑块可以越过右边的滑块位置,不会影响最大值、最小值的输出,但在指定初始值的时候,最小值必须小于等于最大值,否则会显示异常。

on_change参数,可调用类型,拖动任一滑块触发的操作。

更多设计属性参考官方API

3.10.11 ui.joystick

虚拟摇杆控件,生成一个可以交互的虚拟摇杆。

ui_joystick

虚拟摇杆是使用第三方JavaScript库实现的,NiceGUI提供的显式参数不多,想要了解更多用法的话,只能去官方文档了解。

虚拟摇杆支持以下参数:

on_start参数,可调用类型,当用户开始触摸控件时执行的操作。

on_move参数,可调用类型,当用户开始移动摇杆时执行的操作。

on_end参数,可调用类型,当用户停止触摸控件(此时虚拟摇杆消失)时执行的操作。

throttle参数,浮点类型,检测用户移动事件的间隔,默认为0.05,单位秒。

options参数,关键字参数,通过关键字参数的形式传递给此参数,第三方库支持的其他选项,可以参考下表或者官方文档

常用的选项也就以下几个:

color:字符串,虚拟摇杆的颜色。

size:整数,虚拟摇杆的大小。

mode:字符串,虚拟摇杆的显示模式。'dynamic'即动态显示,按下的话,不显示虚拟摇杆。'static'是静态显示,无论是否按下,虚拟摇杆都一直显示。'semi'是半动态,不按下之前,不显示,一旦按下,就会在按下位置始终显示。

shape:字符串,虚拟摇杆的形状,'circle'圆形或者'square'方形。

3.10.12 ui.textarea

文本区域控件其实就是基于ui.input实现的,大部分参数和ui.input的一样。能用的参数主要有这几个:

label参数,字符串类型,直译的话是标签,表示显示在文本区域上方的文本,但不是输入的文本,如果输入的内容是空的,点击输入的之前会显示在文本区域内,点击之后会移动到文本区域上方。

value参数,字符串类型,表示文本区域内的内容,也就是文本区域的值。

on_change参数,可调用类型,表示文本区域的值变化时执行的函数。

placeholder参数,字符串类型,表示文本区域的占位符,即文本区域没有输入之前,显示什么内容。与标签不太一样的是,占位符是文本区域获得焦点时候显示的,标签是文本区域没有获得焦点时候显示的。

validation参数,可调用类型、字典类型或者None,表示验证输入的内容是否有效。如果传入可调用类型参数,该参数返回错误信息表示内容无效,返回None表示内容有效。如果传入字典类型参数,则字典的键(key)表示错误信息,字典的值(value)返回True表示内容有效,返回False则表示内容有效并输出错误信息。默认值为None,表示不验证输入的内容。

3.10.13 ui.number

数字输入框同样是基于ui.input实现的,只不过数字输入框额外支持的几个特有的参数:

label参数,字符串类型,直译的话是标签,表示显示在数字输入框上方的文本,但不是输入的文本,如果输入的内容是空的,点击输入的之前会显示在数字输入框内,点击之后会移动到数字输入框上方。

value参数,浮点类型,表示数字输入框内的内容,也就是数字输入框的值。

on_change参数,可调用类型,表示数字输入框的值变化时执行的函数。

placeholder参数,字符串类型,表示数字输入框的占位符,即数字输入框没有内容的话,显示什么内容。

validation参数,可调用类型、字典类型或者None,表示验证输入的内容是否有效。如果传入可调用类型参数,该参数返回错误信息表示内容无效,返回None表示内容有效。如果传入字典类型参数,则字典的键(key)表示错误信息,字典的值(value)返回True表示内容有效,返回False则表示内容有效并输出错误信息。默认值为None,表示不验证输入的内容。

min参数,浮点类型,数字输入框允许的最小值。

max参数,浮点类型,数字输入框允许的最大值。

precision参数,整数类型,数字输入框内数值的精度。如果是正数,表示保留到小数点之后的位数;如果是负数,表示保留到小数点前的位数,默认不限制精度。

step参数,浮点型,每次点击增减按钮增加、减少的数值。

prefix参数,字符串类型,表示显示在数字前的前缀。

suffix参数,字符串类型,表示显示在数字后的后缀。

format参数,字符串类型,表示数字的显示格式,该字符串采用类似"%.2f"的旧式%格式字符串。注意,不要尝试其他输出非纯浮点数的格式,会导致显示异常,包括但不限于前导填充、正负号等,数字输入框不支持添加正号的浮点数。

如果改变了数字输入框的数字或者精度,导致显示的内容不符合要求,可以手动调用sanitize方法:

3.10.14 ui.color_picker

颜色选择器,可以用于选择指定颜色。为了实现弹出效果,颜色选择器是基于Quasar官方的color_pickermenu实现的。

ui_color_picker

颜色选择器的参数很简单:

on_pick参数,可调用类型,表示颜色选择器完成选择之后执行的操作。

value参数,布尔类型,表示默认颜色选择器的弹出状态,默认为False,即不弹出。

更多设计属性参考官方API

默认的颜色面板提供多种颜色显示模式,想要降低颜色选择复杂度,可以参考API:

ui_color_picker2

3.10.15 ui.color_input

颜色输入框则是颜色选择器与输入框的结合体,从参数上看,也有输入框的影子:

label参数,字符串类型,表示显示在输入框上方的文本。

placeholder参数,字符串类型,表示输入框的占位符,仅在没有颜色值的时候显示。

value参数,字符串类型,表示输入框内的内容,也就是当前选择的颜色的值。

on_change参数,可调用类型,表示输入框的值变化时执行的函数。

preview参数,布尔类型,表示是否让取色器按钮的背景颜色与当前选择的颜色一致,默认为False,即不改变按钮的背景颜色。

ui_color_input

从控件的外观上看,控件主要由三部分组成:主体的输入框、右侧取色器图标代表的按钮、点击按钮之后弹出的颜色选择器面板。与之对应的,便是控件本身、控件的button成员、控件的picker成员,如果想要修改对应部分的外观、属性,可以访问对应的对象进行修改。

比如,想要给按钮增加一个工具提示,代码可以这样写:

ui_color_input2

3.10.16 ui.date

日期控件支持显示、选择日期,用法简单直观,没有太复杂的用法,只是其中的mask参数需要多看看文档:

ui_date

控件支持以下参数:

value参数,字符串类型,控件的初始值。

on_change参数,可调用类型,当控件的值发生变化时执行什么操作。

mask参数,字符串类型,控件值的格式代码,默认为'YYYY-MM-DD'

mask支持的格式代码参考官网文档或者下表:

时间单位格式代码及效果
YY:70,71……29,30
YYYY:1970,1971……2029,2030
M:1,2……11,12
Mo:1st,2nd……11th,12th
MM:01,02……11,12
MMM:Jan,Feb……Nov,Dec
MMMM:January,February……November,December
季度Q:1,2,3,4
Qo:1st,2nd,3rd,4th
本月第几天D:1,2……30,31
Do:1st,2nd……30th,31st
DD:01,02……30,31
本年第几天DDD:1,2……364,365
DDDo:1st,2nd……364th,365th
DDDD:001,002……364,365
本周第几天d:0,1……5,6
do:0th,1st……5th,6th
dd:Su,Mo……Fr,Sa
ddd:Sun,Mon……Fri,Sat
dddd:Sunday,Monday……Friday,Saturday
本周第几天(ISO标准)E:1,2……6,7
本年第几周w:1,2……52,53
wo:1st,2nd……52nd,53rd
ww:01,02……52,53

更多设计属性参考官方API

3.10.17 ui.time

时间控件支持显示、选择时间,用法简单直观,没有太复杂的用法,只是其中的mask参数需要多看看文档:

ui_time

控件支持以下参数:

value参数,字符串类型,控件的初始值。

on_change参数,可调用类型,当控件的值发生变化时执行什么操作。

mask参数,字符串类型,控件值的格式代码,默认为'HH:mm'

mask支持的格式代码参考官网文档或者下表:

时间单位格式代码及效果
H:0,1……22,23
HH:00,01……22,23
h:0,1……11,12
hh:01,02……11,12
m:0,1……58,59
mm:00,01……58,59
s:0,1……58,59
ss:00,01……58,59
小数秒(秒的小数部分)S:0,1……8,9
SS:00,01……98,99
SSS:000,001……998,999
时区Z:-07:00,-06:00……+06:00,+07:00
ZZ:-0700,-0600……+0600,+0700
上下午A:AM,PM
a:am,pm
aa:a.m,p.m
Unix时间戳X:1360013296
x(毫秒):1360013296123

更多设计属性参考官方API

3.10.18 ui.upload

之前提到过ui.download文件下载,有下载就有上传,相比于下载操作,上传操作就复杂了一些。

ui_upload

点击加号按钮,弹出文件选择窗口,选择文件后点确认,然后点击上传按钮,完成上传。

上传控件支持以下参数:

multiple参数,布尔类型,表示是否支持上传多个文件,默认为False

max_file_size参数,整数类型,上传文件的大小限制,单位字节,默认为0,即不限制。

max_total_size参数,整数类型,上传文件的总大小限制,单位字节,默认为0,即不限制。

max_files参数,整数类型,上传文件的数量限制,默认为0,即不限制。

on_upload参数,可调用类型,完成一个文件上传之后执行的操作。

on_multi_upload参数,可调用类型,完成所有文件上传之后执行的操作。

on_rejected参数,可调用类型,当文件的上传被拒绝(超出大小限制或者不符合要求的扩展名等)之后执行的操作。

label参数,字符串类型,显示在控件上部的说明性文字。

auto_upload参数,布尔类型,表示是否开启自动上传,即完成选择之后就上传,默认为False

更多设计属性参考官方API

如果想要读取文件内容,可以读取事件参数的content属性:

ui_upload2

因为底层starlette库默认文件大小参数的设置,上传大文件可能会导致一些潜在的问题。为了确保更平滑地上传大文件,建议调整starletteMultiPartParser 类中的参数max_file_size,将默认的1024 * 1024(1 MB)调大。下面的代码就将该参数调大到5MB,来让更大的文件切片保存到服务器内存中。加大此参数并不是解除大文件的限制,而是让缓存到内存的文件块更大,以便快速处理,不然,文件会直接存入磁盘,可能会产生卡顿现象。另外,此参数也不能无限制加大,此参数过大会导致占用太多的内存,反而会导致内存不足的问题。

3.11 多媒体控件的使用技巧

3.11.1 ui.interactive_image的交互技巧

interactive,顾名思义,就是交互性。既然ui.interactive_image是交互性图像,只是当成普通图像的加强版使用,而不学习它的交互能力实在说不过去。

通过捕获鼠标事件参数,可以获取到鼠标点击的事件种类type、鼠标位置image_x\image_y:

ui_interactive_image_2

既然可以通过点击获取鼠标位置,在鼠标位置画点东西自然也不是问题,还是上面的代码,可以在on_mouse参数的执行函数里,添加一些绘画的内容。前面介绍过,content参数表示覆盖在图片之上的SVG内容,函数里要操作的,也是content属性。其中,使用e.sender.content来获取content属性;因为这个属性实际上是描述SVG内容的字符串,因此每次绘制的内容会自动替换上一次绘制的内容,如果需要保存之前绘制的内容,赋值操作=就要换成增加并赋值操作+=

ui_interactive_image2

对于绘制在content上的SVG图形,其实也有交互事件,ui.interactive_image也能捕捉。只不过,需要设定SVG的pointer-events"all",来确保SVG图形接收所有事件响应,具体含义可以参考手册

目前ui.interactive_image可以捕捉以下SVG事件:

想要在ui.interactive_image中响应SVG的事件,只需订阅"svg:"开头、后接SVG事件名的混合事件名即可响。

ui_interactive_image3

3.12 ui.add_* 和app.add_*的技巧

3.12.1 app.add_static_fileapp.add_static_files

前面提到过ui.imageui.videoui.audio等提供视听效果。不过,前面的例子中只用了网络图片地址,并没有使用本地地址,肯定有读者在尝试使用本地地址之后发现了一个奇怪的现象,以为NiceGUI有bug。

以下面的代码为例,os.path.dirname(os.path.abspath(__file__))可以获取代码文件的当前目录,在代码文件的同目录下放一个图片文件LOGO.png,下面的代码就能显示这个图片。看起来没问题。但是,一旦复制这个图片的地址,将后面的文件名换成其他同目录下的文件名,就会报404错误(文件未找到)。

其实这不是NiceGUI的bug,而是默认的安全和缓存机制,只有代码中使用的静态文件才会生成地址映射,其他没有使用的文件即使存在,直接输入地址访问也会报不存在。以上面的代码为例,图片文件的地址是http://127.0.0.1:8000/_nicegui/auto/static/cf276f9ca066376dc8588fbf61afe905/LOGO.png,中间的cf276f9ca066376dc8588fbf61afe905是图片的hash码,而不是真实存在的目录。之所以会变成这样,是因为NiceGUI会对小的静态文件进行缓存,提高访问速度。因为网页中经常存在大量图片、JavaScript代码、CSS代码等文件,使用缓存可以提高访问速度,不必每次刷新都要从服务器获取。而且,采用缓存机制,还能避免黑客恶意猜测服务器的文件目录,进而获取到影响安全的文件。

这个时候,理解这一切的读者已经恍然大悟,随之而来的是另一个问题——如果想创建一个图片的链接但图片随时修改怎么办?总不能每次都用ui.image生成一次,然后复制地址过去吧?就算使用代码实现自动化,看上去也不够优雅。

这时,就需要正式介绍一下本节要说的功能——app.add_static_fileapp.add_static_file可以返回本地文件的服务器地址,也可以将本地文件映射为固定的服务器地址。

以代码为例:

app_add_static_file

可以看到,给app_add_static_filelocal_file传入本地文件地址,返回的正是服务器地址,和ui.image的图片地址一致,这下,ui.link也能使用图片,而不必担心图片变化还要手动复制地址。

app_add_static_file有三个参数:

local_file参数,字符串类型或者Path类型,表示本地文件地址。

url_path参数,字符串类型,表示服务器地址,默认为None,即自动生成服务器地址,也可以传入参数,例如'/logo.png',就是固定的服务器地址。

single_use参数,布尔类型,表示文件是否在下载一次后移除服务器地址,默认为False

假如要添加的图片比较多,但都在一个文件夹内,是不是还要一个一个添加?不用,app_add_static_files可以将本地文件夹映射为服务器地址。

app_add_static_files有三个参数:

local_directory参数,字符串类型或者Path类型,表示本地文件夹地址。

url_path参数,字符串类型,表示服务器目录地址,必须传入'/'开头的字符串,例如'/pic',同时不能为'/',不然会报错。

follow_symlink参数,布尔类型,表示是否追踪符号链接,即目录下如果存在符号链接的话,会将符号链接代表的实际路径连接到当前路径下,让服务器地址访问符号链接就和本地访问符号链接一样。这个参数默认为False,即不处理符号链接,服务器地址没法访问符号链接。注意,此参数为True并且在Windows平台下的话,代码中使用的os.path.abspath(__file__)会导致获取到文件路径中的磁盘符号为小写,将导致底层代码出错进而上报404错误。此时应该将os.path.abspath(__file__)换成os.path.realpath(__file__)。如果后续遇到Windows平台下开启app_add_static_files的追踪符号链接后,报404错误,可以按照这个思路检查一下传入的local_directory参数中,磁盘符号是不是小写。

3.12.2 app.add_media_fileapp.add_media_files

前面的app.add_static_fileapp.add_static_files用于添加小的静态文件,本节要介绍的app.add_media_fileapp.add_media_files则用于添加媒体文件。看名字的话,和前两者相似,一个是添加单个文件,一个是添加文件夹,那NiceGUI为何要设计重复的功能?

重复当然是不可能重复的,既然是用于媒体文件,肯定与静态文件不同。媒体文件通常是音视频等需要流式传输的文件,不会一下子全部加载,而是一点一点加载,这一点与静态文件不同。毕竟媒体文件通常比较大,一下子全部缓存,一不小心就会让缓存空间爆满。之所以采用流式传输,是因为媒体文件需要支持播放时跳转到指定时间点,如果是采用静态文件那种缓存全部再加载的机制,跳转到指定时间点的功能会失效,只有流式传输才支持跳转到指定时间点。

app.add_media_fileapp.add_media_files得到的服务器地址就是采用流式传输,而不是缓存机制。

以下面的代码为例,可以看一下区别,因为此代码需要本地视频文件,这里就不提供直接运行的代码了,视频文件地址由读者自己修改:

通常情况下,app.add_static_file得到的视频文件地址,在播放器中没法拖动进度条,app.add_media_file得到的视频文件地址,在播放器中和正常播放一样,可以自由拖动时间轴。但是,大多数服务器、浏览器、播放器有容错优化,实际上两种方法得到的视频地址都可以正常播放,只有某些要求严格的接口、播放程序才会有区别。一般建议读者使用app.add_media_file添加媒体文件,以免特定情况下出现不兼容的问题。

app.add_media_file的参数和app.add_static_file的一样,这里不再赘述。app.add_media_files的参数则比app.add_static_files少了follow_symlink参数。

3.12.3 ui.add_head_htmlui.add_body_html

ui.add_head_html可以添加HTML代码到页面head标签内,ui.add_body_html可以添加HTML代码到页面body标签内。对于页面加载来说,head标签内的内容一般不显示,而且因为是从上到下加载,head标签内的内容会先被加载,这里通常放着需要第一时间执行的前置脚本和样式设置。body标签内放着页面显示内容的主体,使用ui.add_body_html会在NiceGUI其他控件加载前嵌入HTML代码,因此ui.add_body_html通常是为了实现在NiceGUI其他内容显示之前放置内容,包括但不限于显示的内容、执行前置脚本和样式设置。

这两个方法都有两个参数字符串参数code和布尔参数shared。前者表示要嵌入的HTML代码,后者表示是否在所有页面(ui.page)执行嵌入操作,对于使用ui.page装饰的页面,后者可以让嵌入操作的代码只写一次,就能应用于所有页面上。

前面说过,ui.html也可以添加HTML代码,那和ui.add_head_htmlui.add_body_html有什么区别?

ui.html会返回一个元素,可以使用一般元素的方法,ui.add_head_htmlui.add_body_html是直接将HTML代码嵌入页面,不会返回任何对象,没法调用一般元素的方法。不过,它们支持嵌入JavaScript代码,而ui.html只能是纯HTML。

3.12.4 ui.add_cssui.add_scssui.add_sass

这三个功能都可以添加CSS代码,只是对应的CSS代码语法规则不同。

ui.add_scssui.add_sass依赖的libsass默认不安装,如果想要使用此控件,可以在项目根目录下使用以下命令安装:

如果运行环境是全局环境或者想用pip安装,可以执行下面的命令安装:

SASS是一款强化 CSS 的辅助工具,它在 CSS 语法的基础上增加了变量 (variables)、嵌套 (nested rules)、混合 (mixins)、导入 (inline imports) 等高级功能,这些拓展令 CSS 更加强大与优雅。简单一点理解的话,SASS是CSS扩展版本。SASS有两种语法风格:以.scss为后缀的,是语法风格和CSS一致的版本,即采用大括号表示所属,用分号表示一句内容的结束;以.sass为后缀的,是语法风格变成用缩进代替大括号、用换行代替分号的版本。

因此ui.add_cssui.add_scssui.add_sass分别代表可以添加标准CSS代码、scss风格代码、sass风格代码。因为scss语法风格和CSS一致,基本兼容CSS,所以,可以用ui.add_scss添加CSS代码,反之不行。

ui.add_css

ui.add_scss

ui.add_sass

3.13 ui.keyboard的事件处理技巧

ui.keyborad可以在页面添加一个按键事件的响应。

ui.keyborad有以下四个参数:

on_key参数,可调用类型,表示发生按键事件之后要执行的函数。

active参数,布尔类型,表示是否激活该功能,默认为True

repeating参数,布尔类型,表示当按键持续按下的时候是否重复发送按键事件,默认为True

ignore参数,字符串列表类型,表示当哪些元素激活时忽略按键事件的响应,默认为['input', 'select', 'button', 'textarea']

对于on_key参数,可以传递一个KeyEventArguments 响应对象作为可调用对象的参数,该对象有以下属性:

为了方便,KeyboardKey对象还有以下属性::

下面代码中,通过勾选、取消复选框来启用、禁用按键事件捕捉。在按键的处理函数里,通过判断Ctrl键是否被按下来决定是否通知另一个按钮的按下、弹起状态:

ui_keyboard

3.14 其他数据展示控件

因为大部分数据展示控件使用的是第三方扩展库,主要用法、API全在第三方网站上,这里只重点讲一下非第三方扩展库的控件。使用扩展库的控件,只简单过一遍基本用法,更多用法则以问题补充的形式添加,就不在教程中细讲。

3.14.1 ui.table

在学习NiceGUI的表格之前,各位读者需要先了解一下HTML的表格结构,等日后熟悉表格控件之后,想要对表格进行深入美化时,很有帮助。

在HTML中,定义表格常用这几种标签:tabletbodytheadtrtdthtable表示整个表格,thead表示表头,tbody表示表格内容主体,tr表示一整行,td表示每个单元格,th表示表头中的单元格。具体结构如下图所示:

table_sketch

在HTML中,想要定义一个表格,需要自己写一堆标签。哪怕有插件,对于每个单元格内的数据,操作起来也没那么简单。

不过,NiceGUI的表格控件简化了绘制的过程,让开发者更加专注于数据的处理,确实是快捷展示数据的一种途径。先看一段简单的代码示例:

ui_table

在正式介绍表格控件的参数用法前,先来学习一下表头定义

columns这个字典通过关键字给Quasar的表头定义传入一系列参数,进而一想到表格控件的展示。在Quasar中,表头定义支持以下参数:

name:表格每一列的独特的id,用字符串表示。这个参数并不是显示出来的表头,只是表示这一列的变量标识符,就和在Python中定义一个变量一样。这个参数后续会用在'body-cell-[name]' slot(插槽)中的namepagination中的sortBy等一系列API中特指name的地方。

label:表示每一列显示的表头内容。如果此参数没有定义,会取rows中每一行的key当表头(这个参考后续介绍的表格最简用法)。

field:表示该列对应每一行的哪个key的数据。看示例代码可知,每一行的数据也是用字典表示,而字典的key对应的就是表头定义里的field。这样,显示在这一列里的,就是每一行的这个key的数据。

required:表示该列的数据是否为必须的。这个值设置为True的列,不受'visible-columns'的影响,会强制显示出来。

设置'visible-columns'属性(props)为字符串列表之后,只有列的name在字符串列表中,列才会显示出来。比如table.props['visible-columns'] = ["age","firstname"]table.props.update(visibleColumns = ["age","firstname"])table.props(''' :visible-columns="['firstname','age']" ''')table.props(f''' :visible-columns="{['firstname','ages']}" ''')。注意,在第二个示例代码中,字典update方法用的关键字,就是将对应属性名的蛇形命名法(使用下划线或连字符分隔每个字段)转化为骆驼命名法(即去掉蛇形命名法的下划线或者连字符之后,除了第一个字段,每个字段的首字母都大写)。另外,该属性只接受JavaScript的字符串列表,直接修改props字典的话,NiceGUI会自动转换。如果是使用props方法传入,不管是使用f-string传入英文引号包起来的Python字符串列表,还是直接传英文引号包起来的字符串列表,结果都是普通字符串,并不会转换为JavaScript的字符串列表(props方法不支持解析Python列表,所以要用英文引号包起来)。因此,在第三、四个示例代码中,需要给被设置的属性添加英文冒号(':')前缀,启用计算表达式功能,让客户端将该属性的值当成JavaScript表达式执行一次,这样就能转换为JavaScript的字符串列表。

align:对齐,表示这一列的对齐方向。

sortable:是否可排序,即点击表头可以启用该列数据的排序。

sort:排序的计算方法,使用JavaScript语法的函数定义。根据(a, b, rowA, rowB) => parseInt(a, 10) - parseInt(b, 10)返回值是否小于0来判断前者是否小于后者。此方法需要JavaScript基础和对相关API的了解,读者可以按需使用。

rawSort:排序的计算方法,使用JavaScript语法的函数定义。根据(a, b, rowA, rowB) => parseInt(a, 10) - parseInt(b, 10)返回值是否小于0来判断前者是否小于后者。此方法需要JavaScript基础和对相关API的了解,读者可以按需使用。

format:使用JavaScript语法的函数定义该列的数据显示为什么格式。比如:

注意,以上三个需要使用JavaScript函数表达式的参数,在Python中传参时,需要在前面加冒号,如:':format':'val => val ? "\u2611":"\u2610"'。这样才能启用计算表达式功能,不然不会生效。

sortOrder:点击表头时,先使用从小到大排序(递增)还是先使用从大到小排序(递减)。只支持'ad'或'da',并且此参数会覆盖'column-sort-order'属性。

下面两个参数只对单元格内容生效:

style:内容样式,采用CSS语法。

classes:内容样式类名。

下面两个参数只对表头生效:

headerStyle:表头样式,采用CSS语法。

headerClasses:表头样式类名。

下面正式介绍table的参数:

rows参数,字典类型,表示表格的内容。字典的键(key)需要对应表头定义的field参数。

columns参数,字典类型,表示表头定义,具体参数含义参考上面的内容或者官网文档。如果没有此参数,表头会自动取rows字典的键(key)。

column_defaults参数,字典类型,表示默认的表头定义。对于每个表头都需要设置的定义,为了减少重复操作的工作量,可以使用此参数传递。

row_key参数,字符串类型,表示确定每行数据唯一性的键(key)值,默认是'id',可以指定。注意,请务必保证指定唯一性键(key)值后,每行数据用该键(key)查询不会重复,否则会导致表格数据异常。

title参数,字符串类型,表格的标题。

selection参数,字符串类型,使用鼠标单击会不会选择对应的行,支持"single"(单选)、"multiple"(多选),默认为None(不选择)。如果启用了单选或者多选,表格控件的selected属性会返回当前选择的行

pagination参数,字典类型或整数类型,如果是整数类型,表示分页时每页几行(0表示无数行,也就是分页常见的单页显示);如果是字典类型,表示分页的定义。默认为None,即不分页。关于分页的定义,支持以下参数:

on_select参数,可调用类型,当选择的行变化时执行什么操作。

on_pagination_change参数,可调用类型,当分页(每页多少行、当前页、排序等相关属性)变化时执行什么操作。

注意,如果启用了单选或者多选,表格控件的selected属性会返回当前选择的行。

别看表格控件涉及到的参数很多,想要快速创建也很简单,只需记住一个参数,那就是rows。以下是最简化的表格用法:

如果想要表格列的动态隐藏,可以给对应列的表头和单元格设置样式'hidden',代码如下:

ui_table2

想要给表格添加数据的话,可以使用add_row方法添加一行,或者用add_rows方法添加多行:

想要在表格中使用来自pandasDataFrame数组,可以使用from_pandas方法:

自定义该列的数据排序方法和数据显示格式,可以使用前面提到的表头定义中的'sort'’format‘

分页的简单示例,前面参数介绍部分已经详细说过,这里直接上代码:

一种根据内容确定显示格式的方法,使用的是'body-cell-{name}'slot而不是'format'参数,可扩展性更强,但要求更属性Quasar框架和前端知识,仅作了解,有余力的可以自行学习。这段代码的作用是根据age是否大于21,来让单元格内的q-badge的显示显示为绿色或者红色:

ui_table3

如果表格的数据比较多,想要通过输入关键字来搜索、筛选数据,可以将输入框的值绑定到表格的filter属性:

ui_table4

3.14.2 ui.tree

树形图控件,可以用树形图的方式展示给定的数据。

先看示例代码:

ui_tree

树形图控件支持以下参数:

nodes参数,字典列表,树的节点层次数据源,通过构建特定结构的数据,可以让树形图按照指定结构显示。列表为根节点,每个列表元素表示一个子节点。在字典中,添加'children'键(key)并设置类似结构的列表为值(value),即可为该节点扩展出子节点。在数据源中,不同的键有不同的含义:

'id':表示节点的编号,具有唯一性,不可重复。如果需要获取已经选择、勾选、展开的节点数据,这个键对应的值会用于标识节点,出现在返回的列表中。

'label':表示节点显示的文字,如果没有对应的值,该节点不会显示文字。

'children':表示节点的子节点,如果有对应的值,该节点会变成可以展开的样式。该键的对应值是字典列表,需要与传给nodes参数结构保持一致,才能确保子节点显示正常。

'icon':表示节点的图标。

node_key参数,字符串类型,表示nodes参数中用于确定节点唯一性的键,默认值是"id",可以根据实际情况修改为其他值。

label_key参数,字符串类型,表示nodes参数中用于显示为节点文本的键,默认值是"label",可以根据实际情况修改为其他值。

children_key参数,字符串类型,表示nodes参数中用于确定子节点的键,默认值是"children",可以根据实际情况修改为其他值。

on_select参数,可调用类型,当节点的选择状态(点击节点即选择、取消选择该节点)时执行的操作。

on_expand参数,可调用类型,当节点的展开状态变化(点击有子节点的节点前的三角形可以展开、收回该节点)时执行的操作。

on_tick参数,可调用类型,当节点的展开状态变化(点击有节点前的复选框可以勾选、取消勾选该节点)时执行的操作。注意,只有下面的tick_strategy参数不为None时,才会显示复选框。

tick_strategy参数,字符串类型,表示节点的复选框采用什么勾选策略,支持"leaf""leaf-filtered""strict",默认为None,即没有勾选策略,不显示复选框。具体勾选策略的含义可以参考下表或者官方文档

策略名含义
"leaf"如果勾选了叶子节点,会影响父节点的勾选状态(变成全选或者部分勾选);如果勾选的是分支节点,会影响父节点的勾选状态(变成全选或者部分勾选)和子节点的勾选状态(可以被勾选子节点会被全选)。
"leaf-filtered"leaf的含义一致,只不过该策略只会影响被过滤之后依然可见的节点。也就是说,对父节点、子节点如果在过滤之后不可见,勾选状态不会改变。
"strict"只影响被勾选的节点,不影响父节点或子节点的勾选状态。

更多设计属性参考官方API

两个助手函数,可以帮助读者更好使用树形图控件,一个是确认给定的键(key)是不是树形图数据源的有效键(key),另一个是根据给定的键(key)获取当前节点、父节点、子节点:

如果想要选择、展开、勾选的方法和获取对应状态的节点,可以参照下表提供的方法:

状态设置为该状态的方法取消该转态的方法获取该状态下的节点
选择.select(key).deselect().props['selected']
展开.expand([key1,...keyn])
不传入参数的话就是对所有节点生效
.collapse([key1,...keyn])
不传入参数的话就是对所有节点生效
.props['expanded']
勾选.tick([key1,...keyn])
不传入参数的话就是对所有节点生效
.untick([key1,...keyn])
不传入参数的话就是对所有节点生效
.props['ticked']

ui_tree2

ui.table类似,树形图也支持筛选:

ui_tree3

3.14.3 ui.log

日志控件,显示新推送的日志消息,并在保留历史日志消息,不需要完整传输历史消息到客户端。

控件只有一个整数参数max_lines,代表日志支持的最大行数。

使用push方法将日志消息推送给控件。

ui_log

以下代码展示了如何给日志记录模块增加推送到日志控件的操作:

3.14.4 ui.editor

编辑器控件,可以提供一个简易的文本编辑器。

ui_editor

placeholder参数,字符串类型,占位符,和输入框的一样。

value参数,字符串类型,编辑器的初始文本。

on_change参数,可调用类型,编辑器内文本变化时执行的操作。

更多设计属性参考官方API

修改content-class属性可以自定义内容区域的样式:

ui_editor3

修改toolbar属性可以实现自定义编辑器的快捷工具栏:

ui_editor2

3.14.5 ui.codemirror

ui.codemirror代码编辑器,功能实现源自CodeMirror,支持超过140种语言的语法高亮(支持列表参考这里)、超过30种界面主题(支持列表参考这里)、行号显示、代码折叠、有限的自动补全等等。

ui_codemirror

控件支持以下参数:

value参数,字符串类型,编辑器的初始值。

on_change参数,可调用类型,编辑器的值发生变化时执行的操作。

language参数,字符串类型,编辑器的语法高亮方案,默认为None。注意,此参数大小写敏感,如果不确定语法的准确名称,可以查询该语言字符串是否在ui.codemirror.supported_languages内。

theme参数,字符串类型,编辑器的主题,默认为"basicLight"。如果不确定主题的准确名称,可以查询ui.codemirror.supported_themes

indent参数,字符串类型,代码缩进使用的字符,默认为4个英文空格。

line_wrapping参数,布尔类型,表示是否自动换行,默认为False

highlight_whitespace参数,布尔类型,表示是否高亮显示出空白字符,默认为False

3.14.6 ui.json_editor

JSON编辑器,功能实现源自svelte-jsoneditor

JSON(JavaScript Object Notation,JavaScript对象表示法)是基于ECMAScript的一个子集设计的,是一种开放标准的文件格式和数据交换格式,它易于人阅读和编写,同时也易于机器解析和生成。JSON独立于语言设计,很多编程语言都支持JSON格式的数据交换。JSON是一种常用的数据格式,在电子数据交换中有多种用途,包括与服务器之间的Web应用程序的数据交换。其简洁和清晰的层次结构有效地提升了网络传输效率,使其成为理想的数据交换语言。其文件通常使用扩展名.json

控件示例如下:

ui_json_editor

控件支持以下参数:

properties参数,字典类型,传入控件的复合属性字典,其键(key)是JSON编辑器支持的属性(参考官方API)。以代码为例介绍几个常用属性:

'content':表示内容主体(官方文档),其值同样是字典。字典包含'json''text'两个键,分别代表内容的JSON格式和TEXT格式。

mode:字符串类型,表示编辑器默认的编辑模式(官方文档),支持'tree''text''table',默认为'tree'

mainMenuBar:布尔类型,表示是否显示编辑器的主菜单栏(官方文档),默认为True

navigationBar:布尔类型,表示是否显示编辑器的导航栏(官方文档),默认为True

statusBar:布尔类型,表示是否显示编辑器文本模式的状态栏(官方文档),默认为True

readOnly:布尔类型,表示是否启用编辑器内容的只读模式(官方文档),默认为True

更多属性和用途参考官方API

on_select参数,可调用类型,当控件的内容被选择时执行的操作。

on_change参数,可调用类型,当控件的内容被改变时执行的操作。

注意,除了初始化指定properties参数外,也可以修改properties属性来进而修改控件,但是,在修改属性之后,需要调用update方法才能触发界面更新,否则会导致界面显示与实际数据不一致。

控件对象的run_editor_method方法可以运行JSON编辑器支持的方法(参考官方API)。示例代码展示了如何展开、收起节点,如何获取数据。

方法名"expand"前的":"表示"path => true"是一个JavaScript表达式,该表达式会先在会在客户端计算之后,再传递给方法。

注意,从客户端获取数据(包括其他一切与客户端相关的操作),只能在ui.page页面内执行,不能在auto-index页面内执行。

ui_json_editor2

3.14.7 ui.scene

场景控件可用于显示3D内容,功能实现源于threejs

示例代码中需要的图片文件和模型文件请自行搜索下载,放到代码文件的同目录下,并根据实际的文件名修改代码:

ui_scene

控件支持以下参数:

width参数,整数类型,控件的宽度,默认为400,单位是像素。在没有缩放控件的情况下,此参数指定了控件的固定宽度。如果使用CSS样式缩放控件宽度,此参数指定的宽度优先级最低。

height参数,整数类型,控件的高度,默认为300,单位是像素。在没有缩放控件的情况下,此参数指定了控件的固定高度。如果使用CSS样式缩放控件高度,此参数指定的高度优先级最低。

grid参数,布尔类型或者整数元组类型,表示是否显示辅助网格或者网格的大小。如果为False,则不显示辅助网格(显示在地平面的网格,用于确定物体的位置和大小)。如果为True,则显示辅助网格。也可以指定一个整数元组来指定尺寸。元组的第一元素表示网格的大小(size),网格为正方形,只需指定边长即可。第二元素表示正方形的边划分为几等分(divisions),这样就可以得到'等分数的平方'个正方形。网格大小的默认值是(100,100)

camera参数,指定场景使用的相机,该参数需要传入ui.scene.perspective_camera(透视相机)或 ui.scene.orthographic_camera(正交相机)的实例,默认为透视相机。两种相机的参数不同:

ui.scene.perspective_camera官方文档):

fov参数,浮点类型,表示视野角(以度为单位),默认为75

near参数,浮点类型,表示近裁剪平面距离,默认值为0.1

far参数,浮点类型,表示远裁剪平面距离,默认值为1000

ui.scene.orthographic_camera官方文档):

size参数,浮点类型,表示视野角,默认为10。此参数与视野角的关系是这样的,官方文档中定义正交相机需要的左、右、顶、底四个视锥平面参数,NiceGUI基于此参数,将 (-size / 2)*控件的宽高比(size / 2) * 控件的宽高比size / 2-size / 2传递给对应参数。

near参数,浮点类型,表示近裁剪平面距离,默认值为0.1

far参数,浮点类型,表示远裁剪平面距离,默认值为1000

on_click参数,可调用类型,点击3D对象时执行的操作。注意,如果想要响应对应的点击事件,需要在click_events参数中添加对应事件的订阅。

click_events参数,字符串列表类型,控件订阅的JavaScript事件,默认为['click', 'dblclick']

on_drag_start参数,可调用类型,开始拖动3D对象时执行的操作。

on_drag_end参数,可调用类型,停止拖动3D对象时执行的操作。

drag_constraints参数,字符串类型,表示用于限制被拖动的3D对象位置的JavaScript表达式,比如:'x = 0, z = y / 2'

background_color参数,字符串类型,场景的背景颜色,默认为"#eee"

如果想要想要点击事件,可以传递一个函数(或者lambda表达式这样的可调用对象)给场景控件的on_click参数。可以传递一个SceneClickEventArguments响应对象作为可调用对象的参数,该对象有以下属性:

对于SceneClickEventHit对象,该对象有以下属性:

ui_scene2

如果想要在物体上呼出右键菜单,需要订阅或者添加订阅'contextmenu'事件。注意,因为右键菜单创建之后属于场景控件,同一个控件只能有一个右键菜单,且菜单不支持动态刷新,如果想要实现右键菜单根据被点击对象显示不同内容,建议清除之前的菜单内容重新添加内容。代码如下:

ui_scene3

使用3D物体的draggable方法可以让对象变成可拖动对象。可以传递一个函数(或者lambda表达式这样的可调用对象)给场景控件的on_drag_start参数和on_drag_end参数,来响应可拖动对象的拖动事件。同时,还可以传递一个SceneDragEventArguments响应对象作为可调用对象的参数,该对象有以下属性:

此外,还可以设置场景控件的drag_constraints参数,来限制可拖动对象的运动规则。代码如下:

ui_scene4

一般来说,为了避免性能问题,拖动物体只会在拖动结束时返回物体的位置等属性。但是,可以使用on方法来显式订阅"drag"事件,以便实时获取物体位置。比如,在下面的代码,长方体的对角位置可以根据两个小球的位置实时刷新:

ui_scene5

可以使用point_cloud对象来渲染点云(具体参见官方文档,但NiceGUI的构造方法与官方有差异)。

point_cloud对象支持三个参数和一个方法:

points参数,浮点类型列表的列表,每个浮点类型列表表示点的坐标,比如,[[1,2,4]]

colors参数,浮点类型列表的列表,每个浮点类型列表表示点的RGB颜色(每个元素取值0-1),比如,[[0.5,0.5,0]]

point_size参数,浮点类型,表示点的尺寸,默认为1.0

set_points方法可以设置、更新点云的点数据,参数是pointscolors,含义同上。

示例如下:

ui_scene6

场景控件因为是基于3D渲染插件,加载过程会比一般的控件慢一些。而有的方法需要等待场景完全加载才能执行。所以,在下面的代码中,使用了await scene.initialized()来等待场景完成加载,然后再执行移动相机的操作:

场景视图控件ui.scene_view可以显示一个指定场景的额外视图。注意,场景视图不同于场景,视图只是显示场景的内容,不能修改场景(无法与场景中的物体、视角交互,比如,拖动和旋转),但可以修改视图的相机来改变视图。此外,目前视图中不能显示2D文字和3D文字。

该控件支持以下参数:

scene参数,ui.scene类型,想要在视图内显示哪个场景的内容。

width参数,整数类型,视图的宽度。

height参数,整数类型,视图的高度。

camera参数,视图使用的相机,该参数需要传入ui.scene.perspective_camera(透视相机)或 ui.scene.orthographic_camera(正交相机)的实例,默认为透视相机。至于两种相机的区别和参数用法,可以参考前面场景的介绍。

on_click参数,和场景的on_click参数一致,点击视图中的物体时执行什么操作。

示例如下:

ui_scene7

使用get_camera方法可以获取当前状态下的相机参数字典,可以从字典中读取position(位置)、rotation(旋转角)、fov(视野角)等信息。完整的字典可以参考下面截取的两种相机的实际数据:

代码则展示了每十秒获取一次相机参数:

对于想要自定义组合3D对象的读者,可以参考下面的代码。示例代码创建了一个自定义类,将模拟三维坐标轴,可以看做是默认axes_helper对象(官网文档)的替代方案。

代码如下:

ui_scene8

3.14.8 ui.aggrid

AG网格控件可以显示表格数据,NiceGUI的AG网格控件的功能实现源自于AG Grid(社区版,暂时不支持企业版)。

不同于ui.table,AG网格支持的功能更多,可以使用run_grid_method方法和run_row_method方法,来与AG网格实例交互,详细用法可以参考官方文档

先看示例:

ui_aggrid

控件支持以下参数:

options参数,字典类型,表示AG网格的选项定义,可以定义网格选项(具体定义内容参考官网文档),网格选项中包含列选项(对应的字典键是'columnDefs',具体定义内容参考官网文档)和具体的行数据(对应的字典键是'rowData')。以示例代码为例,'defaultColDef'表示默认的列定义,适合定义每行都要设置的定义;'rowSelection'表示行的选择方式,可以是单选('single')或者多选('multiple')。

html_columns参数,整数列表,表示哪些列的数据当作HTML格式渲染,默认为空列表,即所有列的数据不当作HTML格式渲染。

theme参数,字符串类型,表示AG网格的显示主题(支持的主题名参考官网文档),默认为'balham'

auto_size_columns参数,布尔类型,表示是否根据表格内容自动调节列宽,默认为True

可以设置列选项的'checkboxSelection'True添加单选或者多选的复选框。选择行之后,可以使用get_selected_rows方法,以字典的形式获取被选择的行。如果是单选,则可以用get_selected_row方法直接获取该行数据或者None(如果没有数据被选择时)。实际上,get_selected_row方法就是直接返回get_selected_rows方法结果的第一个(索引值为0)元素,并在判断为空时返回None

代码如下:

ui_aggrid2

在表头添加迷你过滤器(官网文档),可以用来筛选对应列下的数据。在下面的代码中,除了添加迷你过滤器(也就是漏斗图标),还启用了浮动显示过滤器(也就是第一行的输入框),这两个参数相关的过滤器选项,可以参考官网文档。过滤的类型可以是"agTextColumnFilter"(文本过滤器)、"agNumberColumnFilter"(数字过滤器)、"agDateColumnFilter"(日期过滤器),除了这三种过滤器,还有两种是企业版功能,这里就不介绍了,相关类型可以参考官网文档

示例如下:

ui_aggrid3

列定义中的'cellClassRules'可以实现根据网格的数据,设置网格的CSS类。其值是一个字典,字典的键(key)是要应用的CSS类名,字典的值(value)是用作判断条件的JavaScript表达式,更多用法可以参考官网文档。注意,因为字典的值是JavaScript表达式,需要在字典的键的字符串内添加英文冒号(':'),启用计算表达式功能。

示例代码如下:

ui_aggrid4

当然,如果不想写得这么复杂,可以使用映射的变量代替完整的JavaScript语法,具体参考官网文档

这样的话,只写简单的逻辑表达式就可以了,简化写法如下:

AG网格作为一个表格控件,同样支持从pandas中导入数据,使用from_pandas方法即可:

设定AG网格控件的html_columns参数,可以指定哪几列的数据当作HTML格式:

ui_aggrid5

AG网格控件的所有事件(网格事件列事件行事件)都可以使用on方法订阅,使用Python代码编写事件的响应操作:

ui_aggrid6

如果'rowData'的数据中,键对应的值是字典,则字典被称为子域。那么,列选项的'field'就可以通过英文句号选择'rowData'的子域数据(参考官网文档)。因此,'rowData'的键(key)不能有英文句号。

示例代码如下:

ui_aggrid7

行高除了设置网格选项中的'rowHeight'这种统一值,还可以设置'getRowHeight'这种支持表达式的动态值,更多用法参考官网文档。注意,因为表达式是JavaScript语法,在NiceGUI中,需要添加英文冒号(':')当前缀,启用计算表达式功能。

示例代码如下:

ui_aggrid8

运行run_row_method方法可以调用AG Grid提供的接口方法(官网文档)来影响单行数据,这个方法的参数分别是行ID、接口方法名、传给接口方法的参数。行ID可以是字符串类型的行索引值,也可以是网格选项'getRowId'提供的获取唯一ID的方法。比如,在下面的示例中,':getRowId': '(params) => params.data.name'就是定义了使用name列的数据当作每行数据的唯一ID。

需要注意的是,在点击'UPDATE'按钮、显示的数据更新的同时,行的选择状态是保留的。但是,如果是使用update方法(NiceGUI提供的,在其他更新数据的情况下,为了确保显示正常,可能需要调用此方法)来更新AG网格控件,选择状态则不会保留。

示例代码如下:

ui_aggrid9

除了传入方法名、方法参数给'run_*_method'方法来调用接口方法,还可以给方法名传入定义了JavaScript箭头函数(参考文档)的字符串,可以实现在一定程度上自由运行特定方法。

以下面的代码为例,'g => g.getDisplayedRowAtIndex(0).data'就是定义了一个JavaScript箭头函数,这种函数的用法和Python的lambda表达式一样,本质上是一个函数,只是没有函数名,可以当做函数用,字符串内的'=>'前是参数,后面是该方法的返回值。这个箭头函数的返回值是获取getDisplayedRowAtIndex官网文档)执行结果的data属性。getDisplayedRowAtIndex(0)的执行结果是从显示的行中取第一个(索引值为0),其data属性就是这一行的数据,可以看作是{'name': 'Alice'}这样的字典,这也就是该行代码执行的结果。

需要注意的是,因为这种方法和客户端数据相关,只能在ui.page内使用,不能用在auto-index页面。

示例代码如下:

ui_aggrid10

AG网格有暗黑模式下的主题,但因为是第三方库,所以并不支持Quasar体系的自动切换暗黑模式。但可以使用下面的代码实现手动切换:

ui_aggrid11

3.14.9 ui.highchart

ui.highchart图表控件可以绘制Highchart图表,功能实现源于Highcharts。需要注意的是,该插件商用需要购买商业许可,如有商用需求,请到官网购买许可。同样因为许可限制,NiceGUI将此控件的相关代码放在单独的仓库中维护,如果想要使用此控件,可以在项目根目录下使用以下命令安装:

如果运行环境是全局环境或者想用pip安装,可以执行下面的命令安装:

本控件的主要用法需要读者了解Highcharts,如果没有相关基础,可以先去看看Highcharts官网的入门文档

更新options属性,可以将数据更新推送到图表上。但是,需要注意的是,更新数据之后,需要调用update方法。

先看代码示例:

ui_highchart

控件支持以下参数:

options参数,字典类型,给Highcharts传入的图表选项定义,具体参考官网文档。就示例而言,用到的主要是'title'(标题,布尔类型的False为不显示,可以使用子字典定义标题样式和内容,具体见文档)、'chart'(图表样式,这里使用子字典定义图表类型使用柱状图'bar',如果需要设置其他类型,可以参考绘图选项,而其他图表样式具体见文档)、'xAxis'(设定x轴,具体见文档)、'series'(具体数据,具体见文档)、'credits'(版权信息,具体见文档)。

type参数,字符串类型,表示要绘制的图表大类,默认是"chart"(常规图表),即官网文档中的Highcharts.chart,如果想绘制其他图表。比如Highcharts.stockChart,则需要设置此参数为"stockChart"(股票图)。此参数支持"chart""stockChart""mapChart""ganttChart"等,具体每一种图表大类对应的方法、数据格式均不同,下面仅讲解常规图表。

extras参数,字符串列表,表示需要导入的额外依赖(Highcharts将不常用的功能及其依赖做成了插件,按需导入), 比如"annotations""arc-diagram""solid-gauge"等。

on_point_click参数,可调用类型,表示图表上的点被点击时执行的操作。

on_point_drag_start参数,可调用类型,表示图表上的点开始拖动时执行的操作。

on_point_drag参数,可调用类型,表示图表上的点处于拖动状态时执行的操作。

on_point_drop参数,可调用类型,表示图表上的点放下时执行的操作。

有的图表类型不是常用的类型,需要导入额外依赖,比如下面代码中的'solidgauge',参考官网文档,可知其需要modules/solid-gauge(没有requires的话说明不需要额外导入),所以需要给extras参数传入需要导入的依赖['solid-gauge']

ui_highchart2

如果设置图表允许拖动点,则可以注册以下事件来响应拖动操作:

得到的结果输出(部分)如下:

可以看到,拖动一个点总是包含拖动开始、拖动时、放下三个动作;如果是点击点,则会触发开始拖动和点击这两个动作。

3.14.10 ui.echart

ui.echart可以绘制echart图表,功能实现源自ECharts

相比于HighCharts,ECharts的许可协议更友好,也不需要购买商用许可,而且官网文档有中文版本,因此更受中国用户喜欢。

本控件的主要用法需要读者了解ECharts,如果没有相关基础,可以先去看看ECharts官网的入门文档

更新options属性,可以将数据更新推送到图表上。但是,需要注意的是,更新数据之后,需要调用update方法。

先看代码示例:

ui_echart

控件支持以下参数:

options参数,字典类型,给ECharts传入的图表选项定义,具体参考官网文档

on_click_point参数,可调用类型,当点击数据点时执行的操作。

enable_3d参数,布尔类型,是否强制导入echarts-gl库,这样做会启用3D显示效果。

点击事件的响应操作,可以参考以下代码:

ui_echart2

定义动态属性(即使用JavaScript表达式的内容)的示例代码:

ui_echart3

使用from_pyecharts方法,可以基于pyecharts对象创建图表。对于创建动态属性(即使用JavaScript表达式的内容),既可以像之前说的那样在属性名前加英文冒号':'启用计算表达式功能,也可以使用pyecharts.commons.utilsJsCode方法直接传递转换后的对象,这样就不用添加英文冒号了。

示例代码如下:

ui_echart4

调用run_chart_method方法可以执行ECharts的实例方法(官网文档)。需要注意的是,代码中给实例方法名前面加了英文冒号':',意思就是后面传给这个实例方法的参数是JavaScript表达式,需要先执行之后再传入。

需要注意的是,因为这种方法和客户端数据相关,只能在ui.page内使用,不能用在auto-index页面。

代码示例如下:

ui_echart5

想要添加对ECharts的事件响应的话,可以使用on方法,订阅加了"chart:"前缀的ECharts事件名(具体事件名查询官网文档)。 比如下面的代码,就是监听了ECharts的"selectchanged"事件(官网文档):

ui_echart6

一般来说,如果options参数里包含了字符串"3D",图表会自动启用3D显示。如果没有的话,可以设置enable_3d参数为True来强制启用:

ui_echart7

3.14.11 ui.pyplot

ui.pyplot的用法像是一个控件,但和一般的控件不太一样,它只是创建了一个上下文环境,用于绘制Matplotlib图形。

控件依赖的matplotlib默认不安装,如果想要使用此控件,可以在项目根目录下使用以下命令安装:

如果运行环境是全局环境或者想用pip安装,可以执行下面的命令安装:

控件支持以下参数:

close参数,布尔类型,表示退出上下文环境之后,是否关闭图形(figure,对应控件的fig属性),如果后续需要更新图形的话,此参数设置为False,默认此参数为True

kwargs参数,关键字参数类型,用于给pyplot.figure传入像figsize之类的参数,更多参数支持可以参考官网文档

示例代码如下:

ui_pyplot

3.14.12 ui.matplotlib

ui.matplotlib用法接近ui.pyplot,绘图库用的也是Matplotlib,但少了close参数,自然不能在退出上下文之后继续更新图形。

控件依赖的matplotlib默认不安装,如果想要使用此控件,可以在项目根目录下使用以下命令安装:

如果运行环境是全局环境或者想用pip安装,可以执行下面的命令安装:

参数只有一个:

kwargs参数,关键字参数类型,用于给matplotlib.figure.Figure传入像figsize之类的参数,更多参数支持可以参考官网文档

示例代码如下:

ui_matplotlib

3.14.13 ui.line_plot

ui.line_plot可以绘制折线图,使用pyplot实现,但暴露了一些相关参数,方便创建控件时优化显示效果。

push方法与ui.timer结合,可以做到实时刷新数据。

控件支持以下参数:

n参数,整数类型,图内线条的数量。

limit参数,整数类型,表示每条线支持的最多几个数据点,即如果达到该数量,新的数据点会把最早的数据点顶掉。

update_every参数,整数类型,表示每push多少次数据才会更新图的显示,用来节省CPU和带宽的占用。

close参数,布尔类型,表示退出上下文环境之后,是否关闭图形(figure,对应控件的fig属性),如果后续需要更新图形的话,此参数设置为False,默认此参数为True

kwargs参数,关键字参数类型,用于给pyplot.figure传入像figsize之类的参数,更多参数支持可以参考官网文档

示例代码如下:

ui_line_plot

3.14.14 ui.plotly

ui.plotly可以绘制Plotly支持的图表,功能实现源自Plotly

控件依赖的plotly默认不安装,如果想要使用此控件,可以在项目根目录下使用以下命令安装:

如果运行环境是全局环境或者想用pip安装,可以执行下面的命令安装:

控件支持以下参数:

figure参数,字典类型或者plotly.graph_objects.Figure类型(即传入该类型的对象),表示想要Plotly渲染的图形。plotly.graph_objects.Figure类型的用法可以参考官网文档。字典类型的话,其键(key)就是'data'(文档需要看图形类型,比如typescatter的话,相关文档就是https://plotly.com/javascript/reference/scatter/)、'layout'(用法参考官网文档)、'config'(非必需,用法参考官网文档)等,具体用法可以参考官网文档

如果想要图形的性能好,最好给figure参数传入字典类型。

示例代码如下:

ui_plotly

使用plotly.graph_objects.Figure()add_trace方法(官网文档)之后,需要调用plot.update()或者 ui.update(plot)来更新显示,才能让图形更新。

示例代码如下:

ui_plotly2

使用on方法可以监听Plotly的事件(官网文档),目前,ui.plotly支持监听以下事件:"plotly_click""plotly_legendclick""plotly_selecting""plotly_selected""plotly_hover""plotly_unhover""plotly_legenddoubleclick""plotly_restyle""plotly_relayout""plotly_webglcontextlost""plotly_afterplot""plotly_autosize""plotly_deselect""plotly_doubleclick""plotly_redraw""plotly_animated"

示例代码如下:

ui_plotly3

3.14.15 ui.leaflet

ui.leaflet主要生成一个可以交互的地图控件,功能实现源自Leaflet(官网文档)。

先看代码示例:

ui_leaflet

控件支持以下参数:

center参数,浮点元组类型,表示地图默认中心位置的经纬度坐标。元组的第一个元素是纬度,第二个元素是经度,默认值是(0.0, 0.0)

zoom参数,整数类型,地图的放大等级,默认为13

draw_control参数,布尔类型或者字典类型,是否显示绘图工具栏(实现源自leaflet插件leaflet-draw),默认是False,也可以传入定义绘图工具栏的字典(官网文档)。

options参数,字典类型,传入插件的额外选项,默认为空字典,即不传入任何额外选项。

hide_drawn_items参数,布尔类型,是否隐藏画在地图上元素,默认为False,即不隐藏。

控件默认的地图风格是OpenStreetMap,但可以在这里找到其他地图风格。每次设置风格,都是将新的tile_layer图层(官网文档)叠加上去,所以需要使用clear_layers方法清除之前的地图图层。

示例代码如下:

ui_leaflet2

使用marker方法可以在地图上添加一个标记。以下面的代码为例,通过响应地图的点击事件(所有事件的使用说明参考官网文档),获取点击位置的经纬度,再将经纬度传给marker方法的latlng参数(获取到的是字典,传入的是元组,所以需要转换)。

需要注意的是,这里响应地图的点击事件名是'map-click'。因为地图的一些事件名和JavaScript中事件名相同,所以NiceGUI做了处理,增加了'map-'前缀用于区分,使得控件依然可以正常响应JavaScript中的同名事件。

示例代码如下:

ui_leaflet3

marker方法返回的是marker对象,marker对象的move方法可以移动标记位置:

使用generic_layer方法可以将矢量元素(官网文档)添加到地图中:

ui_leaflet4

控件还支持配置项(官网文档),可以实现禁止控件中的地图显示某些内容或者禁止交互,以便于静态展示:

ui_leaflet5

draw_control参数传入字典可以启用并配置绘图工具栏(官网文档),同时,可以监听‘draw:'为前缀的绘图事件(事件名参考官网文档)来响应绘图操作。

示例代码如下:

ui_leaflet6

可以响应绘图事件'draw:created'的同时,使用图形的点位数据(['layer']['_latlngs'])和自定义的样式选项(官网文档)绘制同样形状、不同样式的图形。不过,这样绘制出来的图会和默认的绘图重叠,需要将hide_drawn_items参数设置为True,来隐藏默认绘图。

示例代码如下:

ui_leaflet7

控件的run_map_method方法可以运行地图对象支持的方法(官网文档),示例代码如下:

运行run_layer_method方法,可以执行指定图层的接口方法。该方法的第一个参数是图层id,第二个参数是接口方法名(详见官网文档),后面的参数是传给接口参数的参数。不过,需要注意的是,该方法与客户端数据相关,只能在ui.page内执行,不能在auto-index页面内使用。

以下面的代码为例,在地图控件完成初始化之后,先设置默认attribution的前缀,然后清除现有的attribution。点击'Add Attribution'按钮,可以调用接口方法(官网文档)增加一个attribution,与先前options里设置的attribution合并,显示为两个attribution。但是,layer接口方法'getAttribution'官网文档)获取的attributionoptions里设置的attribution,因此最后弹出的通知只显示options里设置的attribution

需要额外注意的是,调用任何接口方法都要等地图完成初始化之后,可以使用异步等待控件的initialized方法,就像下面代码中的await m.initialized(),就是为了确保地图确实完成了初始化,而添加的异步等待。

示例代码如下:

ui_leaflet8

3.15 多任务与异步的技巧(2025.01.08更新)

3.15.1 异步(2025.01.08更新)

3.15.1.1 异步支持

什么是异步?为什么要用异步?

与异步相对的是同步,平常在Python中定义、执行的函数,没有async关键字修饰,这种函数就是同步函数。同步函数的特点是,函数中所有执行的过程都是依次进行,哪怕有的操作比较费时,也要等待其执行完毕,才能响应新的代码。异步则不同,如果代码中有耗时的操作,可以使用await关键字标明耗时的操作,这样程序就不会一直等待这些操作的执行结果,可以在等待期间响应新的操作。而耗时的操作也会同时进行着,不会出现同步函数中卡死的情况。

从上面的区别可以看出,面对耗时较久或者不能立刻给出结果的操作,异步可以极大提升程序的处理效率,不容易进入卡死状态,让程序更高效。

只是看文字说明没有直观的感受,那就看一下相似代码的同步、异步的效果。

相信不少读者在初学Python的时候学过这样的操作:使用time.sleep来模拟一些代码执行的过程。虽然Python很慢,但实际执行的时候也不是执行几十秒,所以,使用time.sleep睡眠一段时间,可以让开发者明显感受到代码执行的过程,更方便调试代码。然而,在NiceGUI中,是绝对不能这样来模拟耗时操作的,比如下面示例中的操作:

看起来示例里想要模拟一个耗时操作,让程序在耗时操作开始时、结束时分别发送一次通知。这种情况在开发中很常见,程序往往为了避免耗时操作卡住用户的其他响应,会把耗时操作放到后台,并在完成时通知用户。想法没问题,代码看上去也没问题,可实际执行的时候就有问题:程序没有按照预期显示通知,甚至还出现异常的掉线情况。

可能有的读者会怀疑代码本身有问题,那这样做个验证:注释掉time.sleep(5),看看程序能否正常执行:

实际上代码没有问题,注释掉休眠的代码之后,其余的代码正常执行。这是因为time.sleep是在主线程上执行的,这个休眠操作会阻塞主线程,让负责ui逻辑的主线程被休眠卡住,进而导致界面失去响应。所以,如果想要避免这样的问题,还想模拟耗时操作的话,要用异步休眠来模拟:asyncio.sleep就是异步版本的time.sleep

替换休眠操作之后,代码如下:

当然,既然是异步休眠,自然要用await关键字告诉程序“等待”这个耗时的操作。await只能在异步函数中使用,因此函数在定义时,需要用async修饰。这样修改之后,按钮就可以连续点击了,也不会导致异常或者卡死界面。

实际上,NiceGUI很多控件的响应操作(比如按钮的on_click)都支持异步函数,如果开发中遇到耗时较久或者不能立刻执行完毕的情况,可以先创建好异步函数,让响应操作执行这个异步函数。

3.15.1.2 ui.clipboard和剪贴板

按理来说剪贴板应该早点介绍的,拖到这个时候才说有点晚。不过事出有因,不介绍异步的话,剪贴板是不能正常使用的。因为剪贴板读取的操作就是异步操作,没法立刻得到结果。

异步操作的结果不像同步结果那样可以立即获得,异步操作的返回值是一个可等待对象,必须用await修饰,等待操作完成,才能得到结果。

读取剪贴板的操作是ui.clipboard.read(),本质上是NiceGUI执行了JavaScript代码。几乎所有的JavaScript代码都是异步的,因此,要想正确读取,也必须将此操作放到异步函数内,并用await修饰,比如:

另外需要注意的是,不同用户的剪贴板不能互通,读写剪贴板的操作只能在确定的客户端上执行,因此读写剪贴板的操作只能在ui.page中使用。

下面的示例代码中,ui.page装饰的函数是个异步函数,其实并非必须的,只是第一次读取剪贴板的时候会弹出请求剪贴板的弹窗,如果不使用异步函数,第一次允许时会导致终端报错,但不影响读取;如果是异步函数,函数就会等待这个允许操作,终端不会报错。

在Python中执行读写剪贴板的操作会让服务器执行相关代码,难免给服务器添加额外的压力。这时可以使用JavaScrip的接口读写剪贴板,这样的操作就是由客户端完成,可以减小服务器的压力。当然,JavaScript中同样需要异步读取,所以,JavaScript的实现会复杂一点:

代码中,由JavaScript代码发射一个"clipboard"事件,并把读取结果通过args属性返回Python代码;Python代码监听"clipboard"事件,并接收返回的值,完成剪贴板的读取。

3.15.2 后台任务(2025.01.08更新)

前面说使用time.sleep会阻塞主线程,所以要用异步,但如果是后台任务,不在主线程上运行,那就没问题了。

NiceGUI提供了两种后台执行任务的方法,由run子模块提供:

下面的示例使用time.sleep模拟耗时的操作,但将其放到后台任务中,所以不会卡死主线程:

3.16 版本亮点

3.16.1 app.timer——2.9.0版本新增

NiceGUI官方在2.9.0版本新增了app.timer定时器,虽然用法上和ui.timer一样,但其归属于app而不是ui,还是有所区别的。

为了理解区别,需要先运行以下示例代码:

app_timer_1

示例代码源于NiceGUI官方仓库的一个问题,这里稍微简化了一下。问题作者想要让按钮创建一个定时更新显示内容的定时器,然后用另一个按钮删掉创建定时器的按钮。就是这样听起来很简单的操作,结果在删掉按钮时,工作定时器好像被一并“删掉”了。导致删掉按钮之后,原本应该继续执行的显示更新操作随之停止了。

听起来很奇怪,像是一个问题,其实不是,一开始就没有必要让按钮创建定时器。定时器可以在按钮的响应函数之外创建,按钮只需启动(activate)、停止(deactivate)定时器即可。因为定时器(ui.timer)会自动关联创建定时器的UI组件,一般做法是在auto-index页创建定时器,定时器关联了auto-index页,而auto-index页一般不会被删掉(也不能删掉,会出问题),所以使用定时器不会出问题。如果是其他UI组件创建了定时器,删掉创建定时器的UI组件,同时会一并删掉定时器,这也就是问题的原因。

上面的示例代码更换成常规用法也可以,解决方法也不难,不过,NiceGUI官方还是为此增加了一个独立于UI组件的定时器——app.timer,既是对此问题的解决方案,也是对后续有类似需求的功能实现。

那么,上面的代码在基本不动的前提下,只需将ui.timer换成app.timer即可:

app_timer_2

当然,有了app.timer这种独立于UI组件的定时器之后,以前那种将ui.timer放在ui.page之外,用来充当独立于页面的定时器的方法,就可以使用app.timer了:

两种定时器在这种情景下都不会出问题,这里替换为新的定时器更多是为了区分定时器的作用范围。

4 具体示例【随时更新】

本节主要介绍常见问题,读者可以根据所属模块、函数查阅。

4.1 app.*

4.1.1 app.shutdown

每次关闭程序都要在终端按下Ctrl+C,能不能在用户界面添加一个关闭整个程序的按钮?

通常情况下,NiceGUI程序作为一个网站,不需要关闭。但是,如果是当做桌面程序使用或者有不得不关闭的情况,让用户在终端按下Ctrl+C不太方便,如果程序是以无终端的方式运行,在终端按下Ctrl+C就更不可能。这个时候,可以调用app.shutdown()来关闭整个程序,代码如下:

4.2 app.native

4.2.1 app.native.settings

1,在native mode下,ui.download不能下载怎么办?

因为pywebview默认不允许网页弹出下载,需要使用app.native.settings['ALLOW_DOWNLOADS'] = True修改pywebview的配置,代码如下:

4.3 ui.*

4.3.1 ui.run

1,网站在标题栏的logo是NiceGUI的logo,如何指定为自己的logo?

修改ui.run()的默认参数favicon为自己logo的地址或者emoji字符🚀,例如:ui.run(favicon='🚀')

4.3.2 ui.refreshable

1,为什么有时候创建在ui.refreshable装饰的函数内的控件不会刷新?

以下面代码为例:

先创建了一个ui.card,然后给refreshable修饰的方法传入,在方法内部,想要通过with container的方法,在ui.card内部创建可以刷新的时间标签。然而,实际执行的时候就会发现,标签并没有如预期那样刷新,而是不断创建新的标签。

为什么?

其实,refreshable方法相当于创建了一个可刷新的元素,并将方法内部创建的元素的父元素指定为可刷新元素。每次调用刷新方法,实际上是先清空可刷新元素,然后执行一遍方法内部创建元素的过程。但是,使用with container之后,接下来创建的元素的父元素是container,而不是可刷新元素,因此,每次调用刷新方法之后,方法内部创建的元素不会被清空,反而因为重新创建了一遍元素,container下的元素会多一个。

如果想要实现借用已经创建的元素当容器,让内部元素可以刷新,就要在创建之前,模拟可刷新元素的清空操作:

4.4 ui.button

1,想要在定义之后修改按钮的颜色,但是bg-*的TailWindCSS样式没有用,怎么实现?

按钮的默认颜色由Quasar控制,而Quasar的颜色应用使用最高优先级的!important,TailWindCSS的颜色样式默认比这个低,所以无法成功。如果想修改颜色,可以修改按钮的color属性。或者使用!bg-*来强制应用。代码如下:

注意:Quasar的颜色体系和TailWindCSS的颜色体系不同。Quasar中,使用color-[1-14]来表示颜色,数字表示颜色程度,可选。TailWindCSS中,使用type-color-[50-950]表示颜色,type为功能类别,数字表示颜色程度,可选。需要注意代码中不同方式使用的颜色体系。

2,不擅长CSS的话,怎么用ui.button实现一个 Floating Action Button?

Floating Action Button是特定最小尺寸的圆角按钮,如果熟悉CSS样式的话,可以将普通的按钮改成类似样式,但是,ui.button自带一个fab属性(props),可以一步完成,省去调整CSS的过程,代码如下:

3,如何实现按钮点击后才执行特定操作?

使用异步等待。

4,如何实现嵌入按钮的图标,点击图标并不触发按钮的点击事件?

使用JavaScript中对应事件的stopPropagation()方法阻止事件穿透即可。

4.5 ui.page

1,如何通过传参的形式动态修改页面内容?

使用参数注入,基于FastAPI的https://fastapi.tiangolo.com/tutorial/path-params/https://fastapi.tiangolo.com/tutorial/query-params/ 或者 https://fastapi.tiangolo.com/advanced/using-request-directly/ ,可以捕获url传入的参数,并用在Python程序中。

4.6 ui.stepper

1,如何使用其他控件模拟ui.step

给控件增加.props["name"].props["title"]即可。

2,将ui.stepper的控制按钮放置在外,如何识别第一步和最后一步?

遍历其中控件的name,或者直接指定中间变量存储第一步和最后一步的name,并绑定按钮的可见性或者使用refreshable装饰。

方法一:

方法二:

4.7 ui.icon

1,想用自定义的LOGO图片(SVG格式)当图标行不行?

可以,使用'img:path/to/some_image.png'这样的语法(适用于ui.icon控件或者其他支持icon参数的控件),比如'img:https://cdn.quasar.dev/logo-v2/svg/logo.svg'

4.8 ui.carousel

1,如何自定义轮播图的控制控件?

修改'control'slot(add_slot('control')),API参考Quasar官网

4.9 ui.tree

1,如何实现点击树形图的文字部分也能展开子节点?

可以在on('click')的响应操作中添加对当前节点是否展开的判断,然后展开、收起当前节点。

简洁版:

短小精悍防裁员版: